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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 557e14c1256d2eb630827e7136486ff6ecbcd99f77f96b1ca2ae0909c8cbd818
4
- data.tar.gz: 9d3dc46191a9085f004b5790110aa9ef24b030b8c830fcc6adc06ed0f3317367
3
+ metadata.gz: '069f0c330f3b5a4b0fac6331452c7d9d66c5f92308ad9ffef53454e03602efd3'
4
+ data.tar.gz: 15d1bdacec707645bdc406bab1229253684bf23370f949a7bb6d66b7390aa563
5
5
  SHA512:
6
- metadata.gz: 9df47e33dbcd2696507527b20a907e081f55f94aa6ad9f1f7dfd517c80549fc87dc96ad0d10d7fa761b1a02b71507465e4c8b130ceb556db54736f1e982449aa
7
- data.tar.gz: '039d73f1c388b0c0c1f66f212dcd6abf83a73916690baaecc1127a3c0911da5cb529f841998f68c55a0a280b8468da65bfee6e2d6ac0352d3b97def42ab15e64'
6
+ metadata.gz: 0c8788ad1dc1ba8af7ca402d5f2643eaab1c47c12307a265d427547b00678ce1115482552aa1090d6d560881282ee70eb6b2674b8bb41e03b9f4d302ce2ba47c
7
+ data.tar.gz: dc0b3a38a9f1f9d7dfb272ca3c72634845c9bc2295f198b6731e43249f3ecd2a6610a1ddaf92cb20457443c71e337b4ced9352fe85d41eb641604e12d2eba426
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.0] - 2026-04-24
4
+
5
+ ### Changed
6
+ - Version bump: minor release
7
+
8
+ ### Commits in this release
9
+ - 31e042c feat: Obsidian vault integration with client-side wiki-link resolver and backlinks (#73)
10
+
11
+
12
+ ## [1.2.1] - 2026-04-22
13
+
14
+ ### Changed
15
+ - Version bump: patch release
16
+
17
+ ### Commits in this release
18
+ - 5c04e62 fix(footer,welcome,info): eliminate broken links on bare-minimum sites
19
+
20
+
3
21
  ## [1.2.0] - 2026-04-22
4
22
 
5
23
  ### Changed
@@ -22,6 +40,27 @@
22
40
  ## [Unreleased]
23
41
 
24
42
  ### Added
43
+ - **Obsidian Integration** — The repo's markdown content is now editable as an [Obsidian](https://obsidian.md) vault and rendered identically on GitHub Pages.
44
+ - Shared vault config (`.obsidian/app.json`, `core-plugins.json`, `community-plugins.json`, `appearance.json`, `hotkeys.json`, `templates.json`) and a Templates-compatible note template at `pages/_notes/_templates/note-template.md`.
45
+ - Liquid-generated `assets/data/wiki-index.json` listing every collection document and standalone page (title, basename, permalink, tags, aliases, excerpt) — works on the default GitHub Pages remote_theme build, no plugin whitelist changes required.
46
+ - `assets/js/obsidian-wiki-links.js` — client-side resolver that rewrites `[[wiki-links]]` (with aliases, header anchors, broken-link styling), `![[embeds]]` (image with width modifiers, note transclusion), inline `#tags`, and Obsidian callout blockquotes (`> [!note] Title …`) into Bootstrap-styled HTML.
47
+ - `_includes/content/backlinks.html` — server-side backlinks panel auto-rendered on every `note` layout (and on any page with `backlinks: true`); fully indexable by search engines.
48
+ - `_includes/content/transclude.html` — note embed renderer used by both the JS resolver and the Ruby converter.
49
+ - `_sass/core/_obsidian.scss` (imported via `_sass/custom.scss`) — styles for wiki-links, broken links, callouts, embeds, and the backlinks panel.
50
+ - `_plugins/obsidian_links.rb` — opt-in Ruby converter that performs the same transformations server-side for forks that build with vanilla Jekyll (without the `github-pages` gem) or use a custom GH Actions workflow that bypasses the plugin whitelist.
51
+ - `pages/_docs/obsidian/` — full documentation section: index, getting started, syntax reference, authoring workflow, troubleshooting.
52
+ - `_config.yml` — added `*.canvas` and `*.excalidraw.md` to `exclude:`; `jekyll-redirect-from` enabled to map Obsidian `aliases:` to URL redirects.
53
+ - `.gitignore` — ignore Obsidian's local-only state (`workspace*`, `cache`, `plugins/*/data.json`, `graph.json`, `.trash/`).
54
+ - **Tests**:
55
+ - `test/test_ruby_converter.rb` — 18-test, 65-assertion Minitest suite for `_plugins/obsidian_links.rb` covering wiki-links, embeds, callouts (including fold markers and unknown-type fallback), inline tags, code-block isolation, and a plain-markdown regression guard.
56
+ - `test/test_resolver.js` — 16-assertion Node test for `assets/js/obsidian-wiki-links.js` using a hand-rolled DOM shim; exercises wiki-link resolution, embeds, tags, and DOM-level callout rewriting.
57
+ - `test/test_obsidian.sh` — orchestrator that runs both unit suites and validates that the Jekyll build emits a well-formed `wiki-index.json`. Wired into `test/test_runner.sh`.
58
+ - `test/fixtures/obsidian/sample-note.md` — representative Obsidian note exercising every supported feature.
59
+ - **Obsidian Graph View** — Live, force-directed knowledge graph at `/docs/obsidian/graph/` mirroring Obsidian's local graph view. Built from the same `assets/data/wiki-index.json` that powers the resolver and backlinks panel.
60
+ - `pages/_docs/obsidian/graph.md` — graph page (Bootstrap toolbar with title filter, "Show orphans" switch, "Reset view" button; collection-color legend; usage tips). Cytoscape.js loaded only on this page via CDN with SRI + `crossorigin="anonymous"`.
61
+ - `assets/js/obsidian-graph.js` — vanilla-JS renderer (~330 lines, no build step). Mirrors the resolver's normalization (`toLowerCase().trim()` + whitespace collapse) so wiki-index keys match. `cose` force layout, hover-highlight neighborhood, click-to-navigate (⌘/Ctrl-click for new tab), search-driven node fading, orphans hidden by default with a Bootstrap switch toggle. Broken targets render as dashed red nodes prefixed `__broken__:` in the graph model.
62
+ - `assets/data/wiki-index.json` — extended each entry with an `outgoing: [...]` array of normalized wiki-link targets, extracted at build time via Liquid (masks `![[…]]` embeds, splits on `[[`/`]]`/`|`/`#`/`^`, downcases, dedupes).
63
+ - **Docs**: `README.md` and `AGENTS.md` updated with an Obsidian vault section pointing to the new docs.
25
64
  - **Bare-minimum 3-file remote-theme starter.** Consumers can now publish a
26
65
  fully styled site to GitHub Pages with only `_config.yml`, `Gemfile`, and
27
66
  `index.md` — no installer required. The new `_layouts/welcome.html` shipped
@@ -37,6 +76,37 @@
37
76
  `Your Site Title`, `My Awesome Site`, `Welcome`, `Untitled`, or empty).
38
77
 
39
78
  ### Fixed
79
+ - **Footer Quick Links no longer 404 on bare-minimum sites.**
80
+ `_includes/core/footer.html` previously hard-coded links to
81
+ `/about/`, `/services/`, `/news/`, `/contact/`, `/privacy-policy`, and
82
+ `/terms-of-service` — none of which exist in a 3-file remote-theme
83
+ consumer. Quick Links are now resolved in this order:
84
+ 1. `site.footer_quick_links` (array of `{label, url}`) — explicit override
85
+ 2. Auto-detection: each candidate link only renders if the target page
86
+ exists in `site.html_pages`
87
+ 3. Fallback to `Home` + `Sitemap (XML)` only.
88
+ Privacy Policy / Terms of Service links use the same existence check and
89
+ optionally read from `site.privacy_policy_url` / `site.terms_of_service_url`.
90
+ - **Welcome layout external links now point to existing README anchors.**
91
+ The "Next steps" cards in `_layouts/welcome.html` linked to
92
+ `#content-creation` and `#customisation`, which don't exist in the theme
93
+ README. They now point to `README.md#-quick-start` and
94
+ `README.md#-key-features` respectively.
95
+ - **Theme info admin links are conditional.**
96
+ `_includes/components/info-section.html` previously rendered Admin
97
+ Dashboard links to `/about/config/`, `/about/settings/theme/`,
98
+ `/about/settings/navigation/`, and `/about/settings/environment/`
99
+ unconditionally — guaranteed 404s on bare-minimum sites. The links and
100
+ surrounding section now only render when the corresponding page exists.
101
+ - **Source Code shortcuts skip GitHub buttons when repository is unknown.**
102
+ `_includes/components/dev-shortcuts.html` rendered `https://github.com//blob//`
103
+ URLs when `site.repository` and `site.branch` were empty (typical on bare
104
+ consumer sites). It now hides the GitHub-based buttons and shows a hint
105
+ to set `repository: USER/REPO` in `_config.yml`.
106
+ - **Cookie-consent privacy link is conditional.** The "Learn more in our
107
+ Privacy Policy" anchor in `_includes/components/cookie-consent.html` only
108
+ renders if a `/privacy-policy/` page exists or `site.privacy_policy_url` is
109
+ configured.
40
110
  - **Setup banner link.** `_includes/components/setup-banner.html` no longer
41
111
  points at the non-existent `/404.html`; it now links to
42
112
  `/#setup-wizard`, which is provided by the new welcome layout.
@@ -139,10 +209,18 @@ See [`docs/installation/migration-from-0.x.md`](docs/installation/migration-from
139
209
  - **Roadmap data file**: `_data/roadmap.yml` is now the single source of truth for the project roadmap (versions, status, dates, targets, and feature highlights).
140
210
  - **Roadmap generator**: `scripts/generate-roadmap.rb` (and shell wrapper `scripts/generate-roadmap.sh`) renders a Mermaid gantt diagram and summary table from `_data/roadmap.yml` and injects them into `README.md` between `<!-- ROADMAP_MERMAID:START/END -->` and `<!-- ROADMAP_TABLE:START/END -->` markers. Supports `--check` mode for CI drift detection and `--stdout` for previewing.
141
211
  - **Roadmap sync workflow**: `.github/workflows/roadmap-sync.yml` regenerates the README on push to `main` when the data file or generator changes, and verifies sync on PRs that touch those files.
212
+ - **Local Graph Sidebar Widget** — Per-page mini Obsidian-style local graph rendered at the top of the left sidebar on every page that has one.
213
+ - `_includes/navigation/local-graph.html` — small widget with a "Local graph" heading, a "full ›" link to `/docs/obsidian/graph/`, and a `#obsidian-local-graph` container. Honors `local_graph: false` and `local_graph_depth: N` (default 1) in page front matter.
214
+ - `assets/js/obsidian-local-graph.js` — fetches `wiki-index.json`, finds the current page by `window.location.pathname` (with title/basename fallback), BFS through both outgoing and incoming wiki-links to the configured depth, renders the subgraph with cytoscape.js (`cose` layout sized for ~220px sidebar canvas). Highlights the current page with an orange border + larger node + bold label. Click navigates (⌘/Ctrl-click opens new tab). Hides itself silently if the page isn't in the wiki-index or has no neighbors. Cytoscape is lazy-loaded from CDN with SRI + `crossorigin="anonymous"` and de-duplicated against the full graph page's existing load.
215
+ - `_includes/navigation/sidebar-left.html` — includes the new widget at the top of `.offcanvas-body`, before the nav-mode chain.
216
+ - `_sass/core/_obsidian.scss` — added `.obsidian-local-graph-widget` styles for the 220px container with theme-aware borders/background.
142
217
  - **Docs**: `docs/FORKING.md` — progressive fork → configure → personalize workflow for the `username.github.io` user-site pattern
143
218
  - **Tests**: `test/test_fork_cleanup.sh` — 32-assertion suite covering CLI parsing, dry-run, real cleanup, YAML anchor preservation, and idempotency
144
219
 
145
220
  ### Changed
221
+ - **Obsidian docs**: `pages/_docs/obsidian/syntax-reference.md` — graph view now marked **Available** (was "Not yet implemented"); `pages/_docs/obsidian/index.md` — added Graph view row to the section table.
222
+ - **Documentation cross-linking**: appended a `## See also` block of `[[wiki-links]]` to every page in `pages/_docs/` (76 files — section indexes, leaf pages, and the obsidian cluster) so the graph view shows real cluster structure. Edge count grew from 12 → 292 with 90+ visible nodes after orphan filtering.
223
+ - **Obsidian Graph View polish**: removed the white pill backgrounds behind labels (now halo-only outlines that match the canvas color, so edges read through cleanly); labels hide by default and reveal on zoom-in or hover, while the 37 hub nodes (degree ≥ 6 — Docker, Front Matter, Jeykll, Layouts, Customization, Release Management, etc.) keep their labels always-on as landmarks; loosened `cose` layout (`nodeRepulsion` 8000→18000, `idealEdgeLength` 80→130, added `nodeOverlap: 24` and `componentSpacing: 80`, dropped gravity 0.25→0.18, `numIter` 2500); taller canvas (`82vh` / `620px` min, was `75vh` / `520px`); bigger `cy.fit()` padding (40→70 default, 80→100 for search matches) so top-row labels don't clip the canvas edge.
146
224
  - **README roadmap section** is now auto-generated from `_data/roadmap.yml` instead of being hand-maintained, and includes status, target, and detailed highlight columns.
147
225
  - **`pages/roadmap.md`** rewritten to render the Mermaid gantt chart, release summary, and per-version detail sections directly from `_data/roadmap.yml` via Liquid — so the Jekyll page is always live with the canonical data.
148
226
  - **`_data/README.md`** documents the new `roadmap.yml` data file.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  title: zer0-mistakes
3
3
  sub-title: AI-Native Jekyll Theme
4
4
  description: AI-native Jekyll theme for GitHub Pages — Docker-first development, AI-powered installation, multi-agent integration (Copilot, Codex, Cursor, Claude), AI preview-image generation, and AIEO content optimization with Bootstrap 5.3.
5
- version: 1.2.0
5
+ version: 1.3.0
6
6
  layout: landing
7
7
  tags:
8
8
  - jekyll
@@ -20,7 +20,7 @@ categories:
20
20
  - bootstrap
21
21
  - ai-tooling
22
22
  created: 2024-02-10T23:51:11.480Z
23
- lastmod: 2026-04-22T02:41:59.000Z
23
+ lastmod: 2026-04-24T23:27:06.000Z
24
24
  draft: false
25
25
  permalink: /
26
26
  slug: zer0
@@ -473,6 +473,30 @@ stateDiagram-v2
473
473
  | Pie | `pie` | Distributions |
474
474
  | Git | `gitGraph` | Branch history |
475
475
 
476
+ ### 🧠 Obsidian Vault Integration
477
+
478
+ Edit your content as an [Obsidian](https://obsidian.md) vault — same files,
479
+ same git history, identical rendering on the published site:
480
+
481
+ - **Open repo as a vault**: shared `.obsidian/` config commits with the repo.
482
+ - **Wiki-links** `[[Page Title]]` and aliases `[[Page|Alias]]` resolve to permalinks.
483
+ - **Embeds**: `![[image.png|400]]` for images, `![[Note Title]]` for note transclusion.
484
+ - **Callouts** `> [!note] …` map to Bootstrap alert components.
485
+ - **Backlinks panel** auto-renders on every note (and on any page with `backlinks: true`).
486
+ - **Inline tags** `#topic` link to the tag index, hierarchical tags supported.
487
+ - **Zero plugin requirements**: works on the default GitHub Pages
488
+ `remote_theme` build via a client-side resolver
489
+ (`assets/js/obsidian-wiki-links.js`) backed by a Liquid-generated
490
+ `assets/data/wiki-index.json`.
491
+
492
+ ```bash
493
+ # Open the repo root as an Obsidian vault, edit, then commit & push
494
+ git commit -am "note: today's thinking" && git push
495
+ ```
496
+
497
+ Read the [Obsidian docs](pages/_docs/obsidian/) for setup, syntax reference,
498
+ authoring workflow, and troubleshooting.
499
+
476
500
  ### 📓 Jupyter Notebook Support
477
501
 
478
502
  Seamless integration for data science and computational content:
@@ -1114,7 +1138,7 @@ git push origin feature/awesome-feature
1114
1138
 
1115
1139
  | Metric | Value |
1116
1140
  |--------|-------|
1117
- | **Current Version** | 1.2.0 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
1141
+ | **Current Version** | 1.3.0 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
1118
1142
  | **Documented Features** | 43 ([Feature Registry](https://github.com/bamr87/zer0-mistakes/blob/main/_data/features.yml)) |
1119
1143
  | **Setup Time** | 2-5 minutes ([install.sh benchmarks](https://github.com/bamr87/zer0-mistakes/blob/main/install.sh)) |
1120
1144
  | **Documentation Pages** | 70+ ([browse docs](/pages/)) |
@@ -1165,6 +1189,6 @@ And these AI partners that make zer0-mistakes truly AI-native:
1165
1189
 
1166
1190
  **Built with ❤️ — and a little help from our AI partners — for the Jekyll community**
1167
1191
 
1168
- **v1.2.0** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
1192
+ **v1.3.0** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
1169
1193
 
1170
1194
 
@@ -59,7 +59,11 @@ Configuration: Uses site.posthog settings from _config.yml
59
59
  </p>
60
60
  <p class="mb-2 mb-lg-0 small text-white-50">
61
61
  This website uses cookies and similar technologies to enhance your browsing experience, analyze traffic, and provide personalized content.
62
- <a href="{{ '/privacy-policy/' | relative_url }}" class="text-white text-decoration-underline">Learn more in our Privacy Policy</a>.
62
+ {% assign _pp = site.html_pages | where: "url", "/privacy-policy/" | first %}
63
+ {% unless _pp %}{% assign _pp = site.html_pages | where: "url", "/privacy-policy" | first %}{% endunless %}
64
+ {% if _pp or site.privacy_policy_url %}
65
+ <a href="{{ site.privacy_policy_url | default: '/privacy-policy/' | relative_url }}" class="text-white text-decoration-underline">Learn more in our Privacy Policy</a>.
66
+ {% endif %}
63
67
  </p>
64
68
  </div>
65
69
  <div class="col-12 col-lg-4">
@@ -33,6 +33,11 @@
33
33
  <div class="dev-shortcuts">
34
34
  <div class="d-flex flex-column gap-2">
35
35
 
36
+ {% comment %} Skip all GitHub-based shortcuts on bare/unconfigured sites where
37
+ site.repository or site.branch are empty (otherwise we render
38
+ https://github.com//blob//path → 404). {% endcomment %}
39
+ {% if site.repository and site.branch %}
40
+
36
41
  <!-- Button Group (section title is the parent h6 in info-section.html) -->
37
42
  <div class="btn-group-vertical" role="group" aria-label="Source code shortcuts">
38
43
 
@@ -109,5 +114,13 @@
109
114
 
110
115
  </div>
111
116
 
117
+ {% else %}
118
+ <p class="small text-body-secondary mb-0">
119
+ <i class="bi bi-info-circle me-1"></i>
120
+ Set <code>repository: USER/REPO</code> in your <code>_config.yml</code>
121
+ to enable source-code shortcuts.
122
+ </p>
123
+ {% endif %}
124
+
112
125
  </div>
113
126
  </div>
@@ -108,35 +108,51 @@
108
108
  </div>
109
109
  </div>
110
110
 
111
- <!-- Admin Quick Links -->
111
+ <!-- Admin Quick Links — only render links to pages that actually exist
112
+ in the build (prevents 404s on bare-minimum / remote-theme sites
113
+ that haven't created the admin pages). -->
114
+ {% assign _cfg_page = site.html_pages | where: "url", "/about/config/" | first %}
115
+ {% assign _theme_page = site.html_pages | where: "url", "/about/settings/theme/" | first %}
116
+ {% assign _nav_page = site.html_pages | where: "url", "/about/settings/navigation/" | first %}
117
+ {% if _cfg_page or _theme_page or _nav_page %}
112
118
  <div class="mb-3">
113
119
  <h6 class="text-body-secondary small text-uppercase fw-semibold mb-2">
114
120
  <i class="bi bi-speedometer2 me-1"></i>Admin Dashboard
115
121
  </h6>
116
122
  <div class="list-group list-group-flush small">
123
+ {% if _cfg_page %}
117
124
  <a href="{{ '/about/config/' | relative_url }}" class="list-group-item list-group-item-action d-flex align-items-center px-0">
118
125
  <i class="bi bi-gear me-2 text-body-secondary"></i>Configuration
119
126
  </a>
127
+ {% endif %}
128
+ {% if _theme_page %}
120
129
  <a href="{{ '/about/settings/theme/' | relative_url }}" class="list-group-item list-group-item-action d-flex align-items-center px-0">
121
130
  <i class="bi bi-palette me-2 text-body-secondary"></i>Theme Customizer
122
131
  </a>
132
+ {% endif %}
133
+ {% if _nav_page %}
123
134
  <a href="{{ '/about/settings/navigation/' | relative_url }}" class="list-group-item list-group-item-action d-flex align-items-center px-0">
124
135
  <i class="bi bi-signpost-2 me-2 text-body-secondary"></i>Navigation Editor
125
136
  </a>
137
+ {% endif %}
126
138
  </div>
127
139
  </div>
140
+ {% endif %}
128
141
  </div>
129
142
 
130
143
  <!-- Environment Tab -->
131
144
  <div class="tab-pane fade" id="environment-pane" role="tabpanel" aria-labelledby="environment-tab" tabindex="0">
132
145
  {% include components/env-switcher.html %}
133
146
 
134
- <!-- Admin Link -->
147
+ <!-- Admin Link — only when target page exists -->
148
+ {% assign _env_page = site.html_pages | where: "url", "/about/settings/environment/" | first %}
149
+ {% if _env_page %}
135
150
  <div class="mt-4 pt-3 border-top">
136
151
  <a href="{{ '/about/settings/environment/' | relative_url }}" class="btn btn-outline-secondary btn-sm w-100">
137
152
  <i class="bi bi-box-arrow-up-right me-1"></i>Full Environment &amp; Build Info
138
153
  </a>
139
154
  </div>
155
+ {% endif %}
140
156
  </div>
141
157
 
142
158
  <!-- Developer Tab -->
@@ -31,4 +31,8 @@
31
31
  <script src="{{ '/assets/js/search-modal.js' | relative_url }}"></script>
32
32
 
33
33
  <!-- fffuel-style background customizer (skin switching, toggle, opacity) -->
34
- <script src="{{ '/assets/js/background-customizer.js' | relative_url }}"></script>
34
+ <script src="{{ '/assets/js/background-customizer.js' | relative_url }}"></script>
35
+
36
+ <!-- Obsidian wiki-link / embed / tag client-side resolver (fallback for the
37
+ remote_theme GH Pages build, where _plugins/obsidian_links.rb cannot run). -->
38
+ <script defer src="{{ '/assets/js/obsidian-wiki-links.js' | relative_url }}"></script>
@@ -0,0 +1,103 @@
1
+ {%- comment -%}
2
+ ===================================================================
3
+ BACKLINKS — Pages that wiki-link or markdown-link to the current page
4
+ ===================================================================
5
+
6
+ File: backlinks.html
7
+ Path: _includes/content/backlinks.html
8
+ Purpose: Render an Obsidian-style "Linked mentions" panel.
9
+
10
+ Strategy:
11
+ - Iterate every collection document + standalone page.
12
+ - Match either the current page's permalink in the body, or its title /
13
+ basename inside `[[…]]` wiki-link syntax (case-insensitive, whitespace
14
+ collapsed — same normalization as _plugins/obsidian_links.rb).
15
+ - Skip self-references and drafts.
16
+
17
+ Toggle:
18
+ - Set `backlinks: false` in front matter to suppress.
19
+ - Default: ON for `_layouts/note.html`, OFF for `_layouts/default.html`
20
+ (opt-in via `backlinks: true`).
21
+ ===================================================================
22
+ {%- endcomment -%}
23
+
24
+ {%- if page.backlinks != false -%}
25
+ {%- assign _self_url = page.url -%}
26
+ {%- assign _self_title = page.title | default: "" | downcase | strip -%}
27
+ {%- assign _self_basename = page.path | split: "/" | last | replace: ".md", "" | replace: ".markdown", "" | downcase -%}
28
+
29
+ {%- assign _candidates = "" | split: "" -%}
30
+ {%- for _coll in site.collections -%}
31
+ {%- for _doc in _coll.docs -%}
32
+ {%- assign _candidates = _candidates | push: _doc -%}
33
+ {%- endfor -%}
34
+ {%- endfor -%}
35
+ {%- for _pg in site.pages -%}
36
+ {%- if _pg.output_ext == ".html" -%}
37
+ {%- assign _candidates = _candidates | push: _pg -%}
38
+ {%- endif -%}
39
+ {%- endfor -%}
40
+
41
+ {%- assign _backlinks = "" | split: "" -%}
42
+ {%- for _doc in _candidates -%}
43
+ {%- if _doc.url == _self_url -%}{%- continue -%}{%- endif -%}
44
+ {%- assign _body = _doc.content | default: "" -%}
45
+ {%- assign _body_lower = _body | downcase -%}
46
+ {%- assign _matched = false -%}
47
+
48
+ {%- if _self_url and _self_url != "" and _self_url != "/" -%}
49
+ {%- if _body contains _self_url -%}
50
+ {%- assign _matched = true -%}
51
+ {%- endif -%}
52
+ {%- endif -%}
53
+
54
+ {%- unless _matched -%}
55
+ {%- if _self_title != "" -%}
56
+ {%- assign _needle = "[[" | append: _self_title -%}
57
+ {%- if _body_lower contains _needle -%}
58
+ {%- assign _matched = true -%}
59
+ {%- endif -%}
60
+ {%- endif -%}
61
+ {%- endunless -%}
62
+
63
+ {%- unless _matched -%}
64
+ {%- if _self_basename != "" -%}
65
+ {%- assign _needle2 = "[[" | append: _self_basename -%}
66
+ {%- if _body_lower contains _needle2 -%}
67
+ {%- assign _matched = true -%}
68
+ {%- endif -%}
69
+ {%- endif -%}
70
+ {%- endunless -%}
71
+
72
+ {%- if _matched -%}
73
+ {%- assign _backlinks = _backlinks | push: _doc -%}
74
+ {%- endif -%}
75
+ {%- endfor -%}
76
+
77
+ {%- if _backlinks.size > 0 -%}
78
+ <section class="obsidian-backlinks mt-5 pt-4 border-top" aria-labelledby="backlinks-heading">
79
+ <h3 id="backlinks-heading" class="h5 mb-3">
80
+ <i class="bi bi-link-45deg me-1" aria-hidden="true"></i>
81
+ Linked mentions
82
+ <span class="badge bg-secondary ms-2">{{ _backlinks.size }}</span>
83
+ </h3>
84
+ <ul class="list-unstyled obsidian-backlinks-list">
85
+ {%- for _ref in _backlinks -%}
86
+ <li class="obsidian-backlink mb-2">
87
+ <a href="{{ _ref.url | relative_url }}" class="obsidian-backlink-link">
88
+ <strong>{{ _ref.title | default: _ref.path | split: "/" | last }}</strong>
89
+ </a>
90
+ {%- if _ref.collection -%}
91
+ <span class="badge bg-light text-dark ms-1">{{ _ref.collection }}</span>
92
+ {%- endif -%}
93
+ {%- if _ref.description -%}
94
+ <div class="obsidian-backlink-excerpt small text-muted">
95
+ {{ _ref.description | strip_html | truncate: 160 }}
96
+ </div>
97
+ {%- endif -%}
98
+ </li>
99
+ {%- endfor -%}
100
+ </ul>
101
+ </section>
102
+ {%- endif -%}
103
+ {%- endif -%}
@@ -0,0 +1,62 @@
1
+ {%- comment -%}
2
+ ===================================================================
3
+ TRANSCLUDE — Inline render of an embedded note (![[Note Title]])
4
+ ===================================================================
5
+
6
+ File: transclude.html
7
+ Path: _includes/content/transclude.html
8
+ Purpose: Render the body of another collection document inline.
9
+
10
+ Parameters:
11
+ - target (required): canonical title or basename of the embedded note
12
+ - url (required): resolved permalink (provided by _plugins/obsidian_links.rb)
13
+
14
+ Used by:
15
+ - _plugins/obsidian_links.rb (Ruby converter for `![[Note Title]]`)
16
+
17
+ Notes:
18
+ - We deliberately do NOT recursively run the Obsidian converter on the
19
+ embedded body to avoid transclusion loops.
20
+ - Falls back to a "broken embed" alert if the URL doesn't resolve to a
21
+ known site.documents entry.
22
+ ===================================================================
23
+ {%- endcomment -%}
24
+
25
+ {%- assign _embed_target = include.target -%}
26
+ {%- assign _embed_url = include.url | split: "#" | first -%}
27
+ {%- assign _embedded = nil -%}
28
+ {%- for _doc in site.documents -%}
29
+ {%- if _doc.url == _embed_url -%}
30
+ {%- assign _embedded = _doc -%}
31
+ {%- break -%}
32
+ {%- endif -%}
33
+ {%- endfor -%}
34
+ {%- if _embedded == nil -%}
35
+ {%- for _page in site.pages -%}
36
+ {%- if _page.url == _embed_url -%}
37
+ {%- assign _embedded = _page -%}
38
+ {%- break -%}
39
+ {%- endif -%}
40
+ {%- endfor -%}
41
+ {%- endif -%}
42
+
43
+ {% if _embedded %}
44
+ <aside class="obsidian-embed obsidian-embed-note card my-3" aria-label="Embedded note: {{ _embedded.title | default: _embed_target }}">
45
+ <div class="card-header d-flex justify-content-between align-items-center">
46
+ <span class="obsidian-embed-source">
47
+ <i class="bi bi-link-45deg me-1" aria-hidden="true"></i>
48
+ Embedded: <a href="{{ include.url | relative_url }}">{{ _embedded.title | default: _embed_target }}</a>
49
+ </span>
50
+ {% if _embedded.date %}
51
+ <small class="text-muted">{{ _embedded.date | date: "%Y-%m-%d" }}</small>
52
+ {% endif %}
53
+ </div>
54
+ <div class="card-body obsidian-embed-body">
55
+ {{ _embedded.content | strip_html | truncate: 800 | markdownify }}
56
+ </div>
57
+ </aside>
58
+ {% else %}
59
+ <div class="obsidian-embed obsidian-embed-broken alert alert-warning" role="alert">
60
+ Embed not found: <code>{{ _embed_target | escape }}</code>
61
+ </div>
62
+ {% endif %}
@@ -80,16 +80,33 @@
80
80
  {% if site.email %}<p class="mb-0"><a href="mailto:{{ site.email }}" class="text-light text-decoration-none">{{ site.email }}</a></p>{% endif %}
81
81
  </div>
82
82
 
83
- <!-- Quick Links -->
83
+ <!-- Quick Links
84
+ Resolution order:
85
+ 1. site.footer_quick_links (array of {label, url}) — explicit override
86
+ 2. Auto-detection: only render links whose target pages
87
+ actually exist in the build. Prevents 404s on bare-minimum
88
+ sites that haven't created /about/, /services/, etc. -->
84
89
  <div class="col-12 col-md-6 col-lg-3 mb-3">
85
90
  <h5 class="text-uppercase mb-3">Quick Links</h5>
86
91
  <ul class="list-unstyled" aria-label="Footer quick links">
87
- <li><a href="{{ '/' | relative_url }}" class="text-light text-decoration-none">Home</a></li>
88
- <li><a href="{{ '/about/' | relative_url }}" class="text-light text-decoration-none">About</a></li>
89
- <li><a href="{{ '/services/' | relative_url }}" class="text-light text-decoration-none">Services</a></li>
90
- <li><a href="{{ '/news/' | relative_url }}" class="text-light text-decoration-none">News</a></li>
91
- <li><a href="{{ '/contact/' | relative_url }}" class="text-light text-decoration-none">Contact</a></li>
92
- <li><a href="{{ '/sitemap.xml' | relative_url }}" class="text-light text-decoration-none">Sitemap (XML)</a></li>
92
+ {% if site.footer_quick_links %}
93
+ {% for link in site.footer_quick_links %}
94
+ <li><a href="{{ link.url | relative_url }}" class="text-light text-decoration-none">{{ link.label }}</a></li>
95
+ {% endfor %}
96
+ {% else %}
97
+ <li><a href="{{ '/' | relative_url }}" class="text-light text-decoration-none">Home</a></li>
98
+ {% assign _candidate_links = "About,/about/|Services,/services/|News,/news/|Contact,/contact/" | split: "|" %}
99
+ {% for entry in _candidate_links %}
100
+ {% assign _parts = entry | split: "," %}
101
+ {% assign _label = _parts[0] %}
102
+ {% assign _url = _parts[1] %}
103
+ {% assign _page = site.html_pages | where: "url", _url | first %}
104
+ {% if _page %}
105
+ <li><a href="{{ _url | relative_url }}" class="text-light text-decoration-none">{{ _label }}</a></li>
106
+ {% endif %}
107
+ {% endfor %}
108
+ <li><a href="{{ '/sitemap.xml' | relative_url }}" class="text-light text-decoration-none">Sitemap (XML)</a></li>
109
+ {% endif %}
93
110
  </ul>
94
111
  </div>
95
112
 
@@ -151,8 +168,17 @@
151
168
  </div>
152
169
  <div class="col-md-4 text-md-end">
153
170
  <ul class="list-inline mb-0">
154
- <li class="list-inline-item"><a href="{{ '/privacy-policy' | relative_url }}" class="text-light text-decoration-none">Privacy Policy</a></li>
155
- <li class="list-inline-item"><a href="{{ '/terms-of-service' | relative_url }}" class="text-light text-decoration-none">Terms of Service</a></li>
171
+ {% comment %} Only render policy links whose pages exist (or that are explicitly configured). {% endcomment %}
172
+ {% assign _privacy = site.html_pages | where: "url", "/privacy-policy/" | first %}
173
+ {% unless _privacy %}{% assign _privacy = site.html_pages | where: "url", "/privacy-policy" | first %}{% endunless %}
174
+ {% if _privacy or site.privacy_policy_url %}
175
+ <li class="list-inline-item"><a href="{{ site.privacy_policy_url | default: '/privacy-policy' | relative_url }}" class="text-light text-decoration-none">Privacy Policy</a></li>
176
+ {% endif %}
177
+ {% assign _terms = site.html_pages | where: "url", "/terms-of-service/" | first %}
178
+ {% unless _terms %}{% assign _terms = site.html_pages | where: "url", "/terms-of-service" | first %}{% endunless %}
179
+ {% if _terms or site.terms_of_service_url %}
180
+ <li class="list-inline-item"><a href="{{ site.terms_of_service_url | default: '/terms-of-service' | relative_url }}" class="text-light text-decoration-none">Terms of Service</a></li>
181
+ {% endif %}
156
182
  <li class="list-inline-item">
157
183
  <a href="#" class="text-light text-decoration-none" data-bs-toggle="modal" data-bs-target="#cookieSettingsModal">
158
184
  Cookie Preferences
@@ -0,0 +1,44 @@
1
+ {%- comment -%}
2
+ ===================================================================
3
+ LOCAL GRAPH WIDGET — sidebar mini graph
4
+ ===================================================================
5
+
6
+ File: local-graph.html
7
+ Path: _includes/navigation/local-graph.html
8
+ Purpose: Renders a small "local graph" of the current page and its
9
+ immediate wiki-link neighbors in the left sidebar (Obsidian-
10
+ style local graph view).
11
+
12
+ Behavior:
13
+ - Reads /assets/data/wiki-index.json
14
+ - Finds the current page by window.location.pathname (with title/basename
15
+ fallback for permalink quirks)
16
+ - BFS depth=1 by default (current + direct neighbors, in & out)
17
+ - Hides itself if the current page is not in the wiki-index, has no
18
+ neighbors, or the index can't be loaded
19
+ - Lazily loads cytoscape.js from CDN with SRI + crossorigin
20
+
21
+ Opt-out: set `local_graph: false` in page front matter.
22
+
23
+ Configurable depth: `local_graph_depth: 2` in page front matter (default 1).
24
+ ===================================================================
25
+ {%- endcomment -%}
26
+ {%- if page.local_graph != false -%}
27
+ {%- assign lg_depth = page.local_graph_depth | default: 1 -%}
28
+ <div class="obsidian-local-graph-widget mb-3" aria-label="Local graph">
29
+ <div class="d-flex align-items-center justify-content-between mb-1">
30
+ <span class="text-uppercase fw-semibold small text-secondary">
31
+ <i class="bi bi-diagram-3 me-1" aria-hidden="true"></i>Local graph
32
+ </span>
33
+ <a href="{{ '/docs/obsidian/graph/' | relative_url }}"
34
+ class="small text-decoration-none"
35
+ title="Open the full site graph">full&nbsp;&rsaquo;</a>
36
+ </div>
37
+ <div id="obsidian-local-graph"
38
+ data-depth="{{ lg_depth }}"
39
+ data-index-url="{{ '/assets/data/wiki-index.json' | relative_url }}"
40
+ role="img"
41
+ aria-label="Local graph of pages linked to this page"></div>
42
+ </div>
43
+ <script src="{{ '/assets/js/obsidian-local-graph.js' | relative_url }}" defer></script>
44
+ {%- endif -%}
@@ -48,6 +48,12 @@
48
48
  <!-- Scrollable content area with dynamic navigation options -->
49
49
  <div class="offcanvas-body overflow-auto">
50
50
 
51
+ <!-- ========================== -->
52
+ <!-- LOCAL GRAPH WIDGET -->
53
+ <!-- ========================== -->
54
+ <!-- Mini Obsidian-style local graph of the current page + neighbors -->
55
+ {% include navigation/local-graph.html %}
56
+
51
57
  <!-- ========================== -->
52
58
  <!-- AUTO MODE: Collection Docs -->
53
59
  <!-- ========================== -->
@@ -86,6 +86,15 @@ layout: root
86
86
  <!-- Where the actual page content is rendered -->
87
87
  <div class="bd-content ps-lg-2">
88
88
  {{ content }}
89
+
90
+ {%- comment -%}
91
+ Optional Obsidian-style backlinks panel. Opt-in for the generic
92
+ default layout via `backlinks: true` in front matter (the `note`
93
+ layout enables it by default).
94
+ {%- endcomment -%}
95
+ {% if page.backlinks == true %}
96
+ {% include content/backlinks.html %}
97
+ {% endif %}
89
98
  </div>
90
99
  </main>
91
100
  </div>
data/_layouts/note.html CHANGED
@@ -222,7 +222,13 @@ layout: default
222
222
  {% include content/giscus.html %}
223
223
  </div>
224
224
  {% endif %}
225
-
225
+ <!-- ================================ -->
226
+ <!-- BACKLINKS — "Linked mentions" -->
227
+ <!-- ================================ -->
228
+ <!-- Obsidian-style panel of pages that wiki-link or markdown-link to this note. -->
229
+ <!-- Disable per-page with `backlinks: false` in front matter. -->
230
+ {% include content/backlinks.html %}
231
+
226
232
  </article>
227
233
 
228
234
  <!-- ================================ -->
@@ -272,7 +272,7 @@ is set in `_config.yml`.
272
272
  <code>_about/</code>. Enable them in <code>_config.yml</code>.
273
273
  </p>
274
274
  <a class="btn btn-sm btn-outline-primary"
275
- href="https://github.com/bamr87/zer0-mistakes#content-creation"
275
+ href="https://github.com/bamr87/zer0-mistakes/blob/main/README.md#-quick-start"
276
276
  target="_blank" rel="noopener">
277
277
  Content guide <i class="bi bi-box-arrow-up-right"></i>
278
278
  </a>
@@ -290,7 +290,7 @@ is set in `_config.yml`.
290
290
  your repo to override theme styles without forking.
291
291
  </p>
292
292
  <a class="btn btn-sm btn-outline-success"
293
- href="https://github.com/bamr87/zer0-mistakes#customisation"
293
+ href="https://github.com/bamr87/zer0-mistakes/blob/main/README.md#-key-features"
294
294
  target="_blank" rel="noopener">
295
295
  Customisation <i class="bi bi-box-arrow-up-right"></i>
296
296
  </a>