jekyll-theme-zer0 1.21.0 → 1.23.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/_data/ai.yml +10 -0
  4. data/_data/backlog.yml +173 -23
  5. data/_data/navigation/docs.yml +4 -0
  6. data/_data/theme_backgrounds.yml +24 -0
  7. data/_includes/components/component-showcase.html +83 -14
  8. data/_includes/components/js-cdn.html +10 -2
  9. data/_includes/components/post-card.html +18 -4
  10. data/_includes/components/search-modal.html +28 -4
  11. data/_includes/content/backlinks.html +47 -18
  12. data/_includes/content/giscus.html +30 -44
  13. data/_includes/core/color-mode-init.html +35 -0
  14. data/_includes/core/footer-fabs.html +5 -2
  15. data/_includes/core/head.html +9 -0
  16. data/_includes/core/tokens-inline.html +5 -0
  17. data/_includes/navigation/local-graph-fab.html +1 -1
  18. data/_includes/navigation/local-graph.html +4 -3
  19. data/_includes/navigation/section-sidebar.html +10 -3
  20. data/_includes/obsidian/full-graph.html +7 -5
  21. data/_layouts/article.html +23 -6
  22. data/_layouts/author.html +22 -1
  23. data/_layouts/root.html +15 -1
  24. data/_plugins/obsidian_links.rb +84 -15
  25. data/_sass/core/_obsidian.scss +59 -8
  26. data/assets/data/wiki-index.json +50 -3
  27. data/assets/js/obsidian-graph.js +66 -21
  28. data/assets/js/obsidian-local-graph.js +125 -32
  29. data/assets/js/obsidian-wiki-links.js +118 -21
  30. data/assets/js/search-modal.js +45 -9
  31. data/scripts/bin/giscus-discussions +509 -0
  32. data/scripts/bin/validate +1 -0
  33. data/scripts/ci/classify_changes.py +129 -0
  34. data/scripts/issues/dispatch.py +478 -0
  35. data/scripts/issues/test_verify_close.py +118 -0
  36. data/scripts/issues/triage.py +1046 -0
  37. data/scripts/issues/verify_close.py +179 -0
  38. metadata +10 -2
@@ -2,19 +2,43 @@
2
2
  ===================================================================
3
3
  SEARCH MODAL - Site-wide Search Popup
4
4
  ===================================================================
5
-
5
+
6
6
  File: search-modal.html
7
7
  Path: _includes/components/search-modal.html
8
8
  Purpose: Provide a modal search experience with keyboard shortcut support
9
-
9
+
10
10
  Notes:
11
- - Uses /sitemap/ for query-based search results
11
+ - Form action targets /sitemap/ when that page exists in the build;
12
+ falls back to /sitemap.xml if available, otherwise '#' (safe no-op).
13
+ JS (search-modal.js) always intercepts submit and keeps results
14
+ in-modal, so the action is only a no-JS fallback. The gate here
15
+ prevents a dead link on remote-theme Pages builds that lack /sitemap/.
12
16
  - Keyboard shortcut handled by navigation keyboard module ("/")
13
17
  ===================================================================
14
18
  -->
15
19
 
16
20
  {%- assign ui_text = site.data.ui-text[site.locale] | default: site.data.ui-text.en -%}
17
21
  {%- assign search_placeholder = ui_text.search_placeholder_text | default: "Enter your search term..." -%}
22
+ {%- comment -%}
23
+ Existence-gate the form action to /sitemap/ only when that page is present
24
+ in the build (mirroring the footer Quick Links guard, and the section-sidebar
25
+ /tags/ gate). Remote-theme Pages consumers that haven't committed a /sitemap/
26
+ stub get a safe '#' fallback; the JS always intercepts submit anyway, so this
27
+ only affects the no-JS code path.
28
+ {%- endcomment -%}
29
+ {%- assign _search_sitemap_url = "/sitemap/" -%}
30
+ {%- assign _search_sitemap_page = site.html_pages | where: "url", _search_sitemap_url | first -%}
31
+ {%- unless _search_sitemap_page -%}
32
+ {%- for col in site.collections -%}
33
+ {%- assign _search_sitemap_page = col.docs | where: "url", _search_sitemap_url | first -%}
34
+ {%- if _search_sitemap_page -%}{%- break -%}{%- endif -%}
35
+ {%- endfor -%}
36
+ {%- endunless -%}
37
+ {%- if _search_sitemap_page -%}
38
+ {%- assign _search_form_action = "/sitemap/" | relative_url -%}
39
+ {%- else -%}
40
+ {%- assign _search_form_action = "#" -%}
41
+ {%- endif -%}
18
42
 
19
43
  <div class="modal fade search-modal" id="siteSearchModal" tabindex="-1" aria-labelledby="siteSearchModalLabel" aria-hidden="true">
20
44
  <div class="modal-dialog modal-dialog-centered">
@@ -26,7 +50,7 @@
26
50
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
27
51
  </div>
28
52
  <div class="modal-body">
29
- <form action="{{ '/sitemap/' | relative_url }}" method="get" data-search-form>
53
+ <form action="{{ _search_form_action }}" method="get" data-search-form>
30
54
  <label class="visually-hidden" for="site-search-input">Search</label>
31
55
  <div class="input-group">
32
56
  <span class="input-group-text">
@@ -21,11 +21,26 @@
21
21
  ===================================================================
22
22
  {%- endcomment -%}
23
23
 
24
- {%- if page.backlinks != false -%}
24
+ {%- if page.backlinks != false and site.obsidian.enabled != false -%}
25
25
  {%- assign _self_url = page.url -%}
26
26
  {%- assign _self_title = page.title | default: "" | downcase | strip -%}
27
27
  {%- assign _self_basename = page.path | split: "/" | last | replace: ".md", "" | replace: ".markdown", "" | downcase -%}
28
28
 
29
+ {%- comment -%}
30
+ Needles = every name another page could wiki-link this page by: title,
31
+ file basename, and any frontmatter aliases. Normalized to lowercase + strip
32
+ to match the resolver. A backlink is a [[needle]] with a terminator
33
+ (]] | # ^) so [[Foobar]] does NOT match the needle "foo".
34
+ {%- endcomment -%}
35
+ {%- assign _needles = "" | split: "" -%}
36
+ {%- if _self_title != "" -%}{%- assign _needles = _needles | push: _self_title -%}{%- endif -%}
37
+ {%- if _self_basename != "" -%}{%- assign _needles = _needles | push: _self_basename -%}{%- endif -%}
38
+ {%- for _alias in page.aliases -%}
39
+ {%- assign _an = _alias | downcase | strip -%}
40
+ {%- if _an != "" -%}{%- assign _needles = _needles | push: _an -%}{%- endif -%}
41
+ {%- endfor -%}
42
+ {%- assign _needles = _needles | uniq -%}
43
+
29
44
  {%- assign _candidates = "" | split: "" -%}
30
45
  {%- for _coll in site.collections -%}
31
46
  {%- for _doc in _coll.docs -%}
@@ -46,31 +61,44 @@
46
61
  {%- if _doc.published == false or _doc.draft == true -%}{%- continue -%}{%- endif -%}
47
62
  {%- endunless -%}
48
63
  {%- assign _body = _doc.content | default: "" -%}
49
- {%- assign _body_lower = _body | downcase -%}
50
64
  {%- assign _matched = false -%}
51
65
 
66
+ {%- comment -%} A markdown link to this page's permalink is a backlink. {%- endcomment -%}
52
67
  {%- if _self_url and _self_url != "" and _self_url != "/" -%}
53
68
  {%- if _body contains _self_url -%}
54
69
  {%- assign _matched = true -%}
55
70
  {%- endif -%}
56
71
  {%- endif -%}
57
72
 
73
+ {%- comment -%}
74
+ Wiki-link match: strip fenced (```) and inline (`) code first so a page
75
+ that merely *documents* [[syntax]] doesn't register as a backlink, then
76
+ require a terminator after the needle.
77
+ {%- endcomment -%}
58
78
  {%- unless _matched -%}
59
- {%- if _self_title != "" -%}
60
- {%- assign _needle = "[[" | append: _self_title -%}
61
- {%- if _body_lower contains _needle -%}
79
+ {%- assign _nb = "" -%}
80
+ {%- assign _fp = _body | split: "```" -%}
81
+ {%- for _p in _fp -%}
82
+ {%- assign _m = forloop.index0 | modulo: 2 -%}
83
+ {%- if _m == 0 -%}{%- assign _nb = _nb | append: _p -%}{%- endif -%}
84
+ {%- endfor -%}
85
+ {%- assign _nb2 = "" -%}
86
+ {%- assign _ip = _nb | split: "`" -%}
87
+ {%- for _p in _ip -%}
88
+ {%- assign _m = forloop.index0 | modulo: 2 -%}
89
+ {%- if _m == 0 -%}{%- assign _nb2 = _nb2 | append: _p -%}{%- endif -%}
90
+ {%- endfor -%}
91
+ {%- assign _body_lower = _nb2 | downcase -%}
92
+ {%- for _n in _needles -%}
93
+ {%- assign _v1 = "[[" | append: _n | append: "]]" -%}
94
+ {%- assign _v2 = "[[" | append: _n | append: "|" -%}
95
+ {%- assign _v3 = "[[" | append: _n | append: "#" -%}
96
+ {%- assign _v4 = "[[" | append: _n | append: "^" -%}
97
+ {%- if _body_lower contains _v1 or _body_lower contains _v2 or _body_lower contains _v3 or _body_lower contains _v4 -%}
62
98
  {%- assign _matched = true -%}
99
+ {%- break -%}
63
100
  {%- endif -%}
64
- {%- endif -%}
65
- {%- endunless -%}
66
-
67
- {%- unless _matched -%}
68
- {%- if _self_basename != "" -%}
69
- {%- assign _needle2 = "[[" | append: _self_basename -%}
70
- {%- if _body_lower contains _needle2 -%}
71
- {%- assign _matched = true -%}
72
- {%- endif -%}
73
- {%- endif -%}
101
+ {%- endfor -%}
74
102
  {%- endunless -%}
75
103
 
76
104
  {%- if _matched -%}
@@ -92,11 +120,12 @@
92
120
  <strong>{{ _ref.title | default: _ref.path | split: "/" | last }}</strong>
93
121
  </a>
94
122
  {%- if _ref.collection -%}
95
- <span class="badge bg-light text-dark ms-1">{{ _ref.collection }}</span>
123
+ <span class="badge rounded-pill border text-body-secondary ms-1">{{ _ref.collection }}</span>
96
124
  {%- endif -%}
97
- {%- if _ref.description -%}
125
+ {%- assign _ref_excerpt = _ref.description | default: _ref.excerpt | strip_html | strip | truncate: 160 -%}
126
+ {%- if _ref_excerpt and _ref_excerpt != "" -%}
98
127
  <div class="obsidian-backlink-excerpt small text-muted">
99
- {{ _ref.description | strip_html | truncate: 160 }}
128
+ {{ _ref_excerpt }}
100
129
  </div>
101
130
  {%- endif -%}
102
131
  </li>
@@ -1,49 +1,35 @@
1
1
  <!--
2
- ╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
3
- ║ giscus.html ║
4
- ║ GitHub Discussions Comment System ║
5
- ╠═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
6
- ║ ║
7
- ║ Purpose: Integrates Giscus comment system powered by GitHub Discussions ║
8
- Location: /_includes/content/giscus.html ║
9
- ║ Usage: include content/giscus.html (Liquid tag; braces omitted — Liquid ║
10
- ║ parses include tags even inside HTML comments, which would error)
11
- Dependencies: GitHub repository with Discussions enabled, site.repository, site.giscus configuration ║
12
- ║ ║
13
- Features: ║
14
- ║ • GitHub Discussions-based commenting system ║
15
- ║ • Automatic theme detection (preferred_color_scheme) ║
16
- Pathname-based mapping for unique comment threads ║
17
- Reaction support for enhanced user engagement ║
18
- Top-positioned input for better UX ║
19
- ║ ║
20
- ║ Configuration Options: ║
21
- ║ • data-repo: GitHub repository (site.repository) ║
22
- ║ • data-repo-id: GitHub repository ID (site.giscus.data-repo-id) ║
23
- ║ • data-category-id: Discussions category ID (site.giscus.data-category-id) ║
24
- ║ • data-mapping: "pathname" - maps comments to page URL ║
25
- ║ • data-strict: "1" - enables strict mapping mode ║
26
- ║ ║
27
- ║ Setup Requirements: ║
28
- ║ • GitHub repository with Discussions feature enabled ║
29
- ║ • Giscus app installed on the repository ║
30
- ║ • Correct repo-id and category-id in _config.yml ║
31
- ║ ║
32
- ║ Reference: Feature Request #FR000001 ║
33
- ║ Documentation: https://giscus.app/ ║
34
- ║ ║
35
- ╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
36
- -->
2
+ giscus.html — GitHub Discussions comment system
3
+ ===============================================
4
+
5
+ Purpose: Embeds the Giscus widget so page comments are stored as
6
+ GitHub Discussions (no database, privacy-friendly).
7
+ Location: _includes/content/giscus.html
8
+ Usage: Rendered by the article / note / notebook layouts, gated on
9
+ `page.comments != false and site.giscus.enabled`.
10
+ To include manually elsewhere, use the FULL path:
11
+ include content/giscus.html (wrap in Liquid tags)
12
+ Dependencies: site.repository, site.giscus.* (see _config.yml), a public
13
+ repo with Discussions enabled + the Giscus app installed.
14
+
15
+ Features:
16
+ GitHub Discussions-based commenting
17
+ Automatic light/dark theme (preferred_color_scheme)
18
+ Pathname-based mapping for one thread per page (data-strict)
19
+ • Reactions enabled, input positioned at the top
37
20
 
38
- <!-- ===================================================================================================== -->
39
- <!-- GISCUS COMMENT SYSTEM -->
40
- <!-- ===================================================================================================== -->
41
- <!-- GitHub Discussions-powered comment system integration -->
42
- <!-- Feature Request: #FR000001 -->
43
- <!-- Documentation: https://giscus.app/ -->
44
- <!-- Async loading: Non-blocking comment system initialization -->
21
+ Config-driven attributes (from _config.yml `giscus:` block):
22
+ data-repo ← site.repository
23
+ data-repo-id ← site.giscus.data-repo-id
24
+ data-category-id site.giscus.data-category-id
45
25
 
46
- <!-- giscus app - https://giscus.app/ - Feature # FR000001 -->
26
+ Docs: pages/_docs/features/giscus-comments.md
27
+ Reference: https://giscus.app/ (Feature Request #FR000001)
28
+
29
+ NOTE: do not write a bare `include giscus.html` example with live Liquid
30
+ braces inside this file — Liquid evaluates tags even inside HTML comments,
31
+ which would recursively (and wrongly) try to include "giscus.html".
32
+ -->
47
33
 
48
34
  <script src="https://giscus.app/client.js"
49
35
  data-repo="{{ site.repository }}"
@@ -58,4 +44,4 @@
58
44
  data-lang="en"
59
45
  crossorigin="anonymous"
60
46
  async>
61
- </script>
47
+ </script>
@@ -0,0 +1,35 @@
1
+ {%- comment -%}
2
+ Component: color-mode-init
3
+ Path: _includes/core/color-mode-init.html
4
+ Purpose: Early inline script that applies data-bs-theme BEFORE Bootstrap CSS
5
+ loads, preventing FOUC (flash of unstyled/wrong-themed content).
6
+ Params: none — reads data-color-mode-default from the <html> element,
7
+ which root.html sets from site.color_mode_default.
8
+ Depends on: root.html (sets data-color-mode-default attribute server-side)
9
+ Notes: Must be included BEFORE any Bootstrap CSS link tag. The script
10
+ is tiny, synchronous, and runs inline before the browser requests
11
+ external stylesheets, so the correct theme is already applied
12
+ when Bootstrap's [data-bs-theme] selectors are first evaluated.
13
+
14
+ Priority order (highest → lowest):
15
+ 1. localStorage["theme"] — user's explicit override via Appearance panel
16
+ 2. data-color-mode-default — site.color_mode_default config value
17
+ 3. "auto" fallback — resolves via prefers-color-scheme
18
+ {%- endcomment -%}
19
+ <script>
20
+ (function () {
21
+ 'use strict';
22
+ try {
23
+ var root = document.documentElement;
24
+ var configDefault = root.getAttribute('data-color-mode-default') || 'auto';
25
+ var stored = window.localStorage.getItem('theme');
26
+ var mode = stored || configDefault;
27
+ if (mode === 'auto') {
28
+ root.setAttribute('data-bs-theme',
29
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
30
+ } else {
31
+ root.setAttribute('data-bs-theme', mode);
32
+ }
33
+ } catch (e) { /* localStorage / matchMedia unavailable — server-rendered value stands */ }
34
+ })();
35
+ </script>
@@ -23,6 +23,9 @@
23
23
  {%- assign _root_only_layouts = "landing,welcome,stats,sitemap-collection,setup,section,news,index,home,admin" | split: "," -%}
24
24
  {%- unless _root_only_layouts contains page.layout -%}
25
25
  {% include navigation/toc-fab.html %}
26
- {% include navigation/local-graph-fab.html %}
27
- {% include navigation/local-graph.html %}
26
+ {%- comment -%} Local-graph FAB/panel are part of the Obsidian feature. {%- endcomment -%}
27
+ {%- if site.obsidian.enabled != false -%}
28
+ {% include navigation/local-graph-fab.html %}
29
+ {% include navigation/local-graph.html %}
30
+ {%- endif -%}
28
31
  {%- endunless -%}
@@ -127,6 +127,15 @@ window.MathJax = {
127
127
  <!-- CSS FRAMEWORKS AND LIBRARIES -->
128
128
  <!-- ================================ -->
129
129
 
130
+ <!-- ========================== -->
131
+ <!-- COLOR MODE INIT (FOUC FIX) -->
132
+ <!-- ========================== -->
133
+ <!-- Must run BEFORE Bootstrap CSS so the correct data-bs-theme is applied
134
+ when [data-bs-theme=dark/light] selectors are first evaluated.
135
+ Reads site.color_mode_default (via data-color-mode-default attr on <html>)
136
+ and falls back to localStorage["theme"] for the user's explicit preference. -->
137
+ {% include core/color-mode-init.html %}
138
+
130
139
  <!-- ========================== -->
131
140
  <!-- BOOTSTRAP 5 FRAMEWORK -->
132
141
  <!-- ========================== -->
@@ -35,6 +35,11 @@
35
35
  {%- endif -%}
36
36
 
37
37
  {%- comment -%}
38
+ NOTE: The data-bs-theme FOUC-prevention script lives in
39
+ _includes/core/color-mode-init.html (included from head.html BEFORE Bootstrap
40
+ CSS) so the correct theme is applied before any [data-bs-theme] selectors are
41
+ evaluated. Do not duplicate it here.
42
+
38
43
  Restore any user-saved Appearance overrides before paint to avoid a flash
39
44
  of the default palette. The Appearance panel (Track 6) writes JSON like
40
45
  {"primary":"#ff5722"} to localStorage["zer0-appearance"].
@@ -11,7 +11,7 @@
11
11
  {%- endif -%}
12
12
  {%- endif -%}
13
13
  {%- if page.local_graph != false and _effective_sidebar != false -%}
14
- <div class="obsidian-local-graph-fab d-print-none" data-obsidian-local-graph-toggle>
14
+ <div id="obsidianLocalGraphFab" class="obsidian-local-graph-fab d-print-none" data-obsidian-local-graph-toggle hidden>
15
15
  <button class="btn btn-primary rounded-circle shadow-lg p-0 obsidian-local-graph-toggle"
16
16
  type="button"
17
17
  data-bs-toggle="offcanvas"
@@ -40,7 +40,8 @@
40
40
  aria-labelledby="obsidianLocalGraphLabel"
41
41
  data-bs-scroll="true"
42
42
  data-bs-backdrop="false"
43
- data-obsidian-local-graph-panel>
43
+ data-obsidian-local-graph-panel
44
+ hidden>
44
45
  <div class="offcanvas-header">
45
46
  <h2 class="offcanvas-title h5 mb-0" id="obsidianLocalGraphLabel">
46
47
  <i class="bi bi-diagram-3 me-2" aria-hidden="true"></i>Local graph
@@ -80,7 +81,7 @@
80
81
  <div id="obsidian-local-graph"
81
82
  data-depth="{{ lg_depth }}"
82
83
  data-index-url="{{ '/assets/data/wiki-index.json' | relative_url }}"
83
- role="img"
84
+ role="group"
84
85
  aria-label="Local graph of pages linked to this page"></div>
85
86
  <p class="obsidian-local-graph-status small text-secondary mt-2 mb-0"
86
87
  data-obsidian-local-graph-status
@@ -88,5 +89,5 @@
88
89
  </div>
89
90
  </div>
90
91
  </aside>
91
- <script src="{{ '/assets/js/obsidian-local-graph.js' | relative_url }}" defer></script>
92
+ <script src="{{ '/assets/js/obsidian-local-graph.js' | relative_url }}?v={{ site.time | date: '%s' }}" defer></script>
92
93
  {%- endif -%}
@@ -64,9 +64,14 @@
64
64
  {% endfor %}
65
65
  </nav>
66
66
  </div>
67
- {% if sub_categories.size > 15 %}
67
+ {% comment %} Existence-gate the tags index: a remote-theme consumer without
68
+ the (plugin-generated) /tags/ page must not get a 404 link. Mirrors the
69
+ footer Quick-Links guard and honours the documented site.tags_page. {% endcomment %}
70
+ {% assign _tags_url = site.tags_page | default: "/tags/" %}
71
+ {% assign _tags_page = site.html_pages | where: "url", _tags_url | first %}
72
+ {% if sub_categories.size > 15 and _tags_page %}
68
73
  <div class="card-footer bg-transparent">
69
- <a href="{{ site.baseurl }}/tags/" class="btn btn-sm btn-outline-secondary w-100">
74
+ <a href="{{ _tags_url | relative_url }}" class="btn btn-sm btn-outline-secondary w-100">
70
75
  View All Tags <i class="bi bi-arrow-right ms-1"></i>
71
76
  </a>
72
77
  </div>
@@ -123,11 +128,13 @@
123
128
  {% endif %}
124
129
  {% endfor %}
125
130
  </nav>
131
+ {% if _tags_page %}
126
132
  <div class="p-3">
127
- <a href="{{ site.baseurl }}/tags/" class="btn btn-outline-primary w-100">
133
+ <a href="{{ _tags_url | relative_url }}" class="btn btn-outline-primary w-100">
128
134
  <i class="bi bi-tags me-2"></i>Browse All Tags
129
135
  </a>
130
136
  </div>
137
+ {% endif %}
131
138
  </div>
132
139
  </div>
133
140
 
@@ -136,7 +136,7 @@ and [backlinks panel]({{ "/docs/obsidian/syntax-reference/#backlinks-panel" | re
136
136
  <span id="obsidian-graph-status" role="status"></span>
137
137
  </div>
138
138
 
139
- <div id="obsidian-graph" role="img" aria-label="Site knowledge graph"></div>
139
+ <div id="obsidian-graph" role="group" aria-label="Site knowledge graph"></div>
140
140
 
141
141
  <div class="obsidian-graph-legend" aria-label="Graph legend">
142
142
  <span><span class="swatch" style="background:#0d6efd"></span>Posts</span>
@@ -167,10 +167,12 @@ unresolved targets show up as dashed red nodes so you can find dangling
167
167
  links at a glance. The graph is regenerated every Jekyll build; nothing
168
168
  runs client-side except cytoscape's force layout.
169
169
 
170
- <!-- Cytoscape.js (vendored, only loaded on this page) no CDN so the graph
171
- works under strict CSP and offline. See assets/vendor/cytoscape/. -->
172
- <script src="{{ '/assets/vendor/cytoscape/cytoscape.min.js' | relative_url }}" defer></script>
173
- <script src="{{ '/assets/js/obsidian-graph.js' | relative_url }}" defer></script>
170
+ <!-- Cytoscape.js is vendored (no runtime CDN) and lazy-loaded by
171
+ obsidian-graph.js from window.OBSIDIAN_CONFIG.cytoscapeUrl, only on this
172
+ page, with an in-page recovery message if the fetch fails. -->
173
+ {% if site.obsidian.enabled != false %}
174
+ <script src="{{ '/assets/js/obsidian-graph.js' | relative_url }}?v={{ site.time | date: '%s' }}" defer></script>
175
+ {% endif %}
174
176
 
175
177
  ## See also
176
178
 
@@ -160,16 +160,33 @@ layout: default
160
160
  </div>
161
161
 
162
162
  <!-- Category Link -->
163
- {% comment %} Category base is configurable (`category_base`, default `/news`)
164
- so remote-theme consumers whose category index lives elsewhere
165
- (e.g. `/categories`) don't get a hardcoded 404. See issue #204. {% endcomment %}
163
+ {% comment %} Category base is configurable (`category_base`, default `/news`).
164
+ Only link the badge when that category index page actually exists in the
165
+ build; on a remote-theme Pages consumer the category section is
166
+ plugin/page-generated and absent, so we render a plain badge instead of a
167
+ link that 404s (mirrors the tag-badge and footer Quick-Links guard).
168
+ See issue #204. {% endcomment %}
166
169
  {% if page.categories.size > 0 %}
167
170
  {% assign _category_base = site.category_base | default: '/news' %}
171
+ {% assign _primary_category = page.categories | first %}
172
+ {% capture _category_url %}{{ _category_base }}/{{ _primary_category | slugify }}/{% endcapture %}
173
+ {% comment %} Category index pages can live in site.pages (a regular page) or
174
+ site.posts (a dated section index), so check both. {% endcomment %}
175
+ {% assign _category_page = site.html_pages | where: "url", _category_url | first %}
176
+ {% unless _category_page %}
177
+ {% assign _category_page = site.posts | where: "url", _category_url | first %}
178
+ {% endunless %}
168
179
  <div class="mb-3">
169
- <a href="{{ site.baseurl }}{{ _category_base }}/{{ page.categories | first | slugify }}/"
180
+ {% if _category_page %}
181
+ <a href="{{ site.baseurl }}{{ _category_url }}"
170
182
  class="badge bg-primary text-decoration-none fs-6">
171
- <i class="bi bi-folder me-1"></i>{{ page.categories | first }}
183
+ <i class="bi bi-folder me-1"></i>{{ _primary_category }}
172
184
  </a>
185
+ {% else %}
186
+ <span class="badge bg-primary fs-6">
187
+ <i class="bi bi-folder me-1"></i>{{ _primary_category }}
188
+ </span>
189
+ {% endif %}
173
190
  </div>
174
191
  {% endif %}
175
192
 
@@ -512,7 +529,7 @@ layout: default
512
529
  <!-- ================================ -->
513
530
  <!-- COMMENT SYSTEM -->
514
531
  <!-- ================================ -->
515
- {% if page.comments != false and site.giscus %}
532
+ {% if page.comments != false and site.giscus.enabled %}
516
533
  <section class="post-comments mt-5 pt-4 border-top" id="comments">
517
534
  <h2 class="h4 mb-4">
518
535
  <i class="bi bi-chat-dots me-2"></i>Comments
data/_layouts/author.html CHANGED
@@ -68,10 +68,31 @@ hide_intro: true
68
68
  <!-- ========================== -->
69
69
  <!-- BREADCRUMB -->
70
70
  <!-- ========================== -->
71
+ {%- comment -%}
72
+ Only link the /authors/ breadcrumb crumb when that index page actually exists
73
+ in the build. A pure remote-theme Pages consumer doesn't get the
74
+ plugin-generated /authors/ index, so rendering the link would produce a 404.
75
+ Mirror the existence guard used in _includes/navigation/breadcrumbs.html
76
+ and _includes/components/author-bio.html. See issue #204.
77
+ {%- endcomment -%}
78
+ {%- assign _authors_url = '/authors/' -%}
79
+ {%- assign _authors_page = site.html_pages | where: "url", _authors_url | first -%}
80
+ {%- unless _authors_page -%}
81
+ {%- for _col in site.collections -%}
82
+ {%- assign _authors_page = _col.docs | where: "url", _authors_url | first -%}
83
+ {%- if _authors_page -%}{%- break -%}{%- endif -%}
84
+ {%- endfor -%}
85
+ {%- endunless -%}
71
86
  <nav aria-label="breadcrumb" class="mb-3">
72
87
  <ol class="breadcrumb">
73
88
  <li class="breadcrumb-item"><a href="{{ '/' | relative_url }}">Home</a></li>
74
- <li class="breadcrumb-item"><a href="{{ '/authors/' | relative_url }}">Authors</a></li>
89
+ <li class="breadcrumb-item">
90
+ {%- if _authors_page -%}
91
+ <a href="{{ _authors_url | relative_url }}">Authors</a>
92
+ {%- else -%}
93
+ Authors
94
+ {%- endif -%}
95
+ </li>
75
96
  <li class="breadcrumb-item active" aria-current="page">{{ author_name }}</li>
76
97
  </ol>
77
98
  </nav>
data/_layouts/root.html CHANGED
@@ -31,7 +31,21 @@
31
31
  -->
32
32
 
33
33
  <!doctype html>
34
- <html lang="{{ site.locale | slice: 0,2 | default: 'en' }}" class="no-js" data-bs-theme="dark" data-theme-skin="{{ site.theme_skin | default: 'dark' }}" data-zer0-bg="{{ site.theme_background.enabled | default: true }}">
34
+ {%- comment -%}
35
+ Resolve the server-side data-bs-theme:
36
+ dark → dark
37
+ light → light
38
+ auto → dark (inline script in tokens-inline.html corrects this before first paint)
39
+ We also store the raw config value in data-color-mode-default so the inline
40
+ script can read it without a Liquid evaluation.
41
+ {%- endcomment -%}
42
+ {%- assign _cm = site.color_mode_default | default: 'auto' -%}
43
+ {%- if _cm == 'light' -%}
44
+ {%- assign _bs_theme = 'light' -%}
45
+ {%- else -%}
46
+ {%- assign _bs_theme = 'dark' -%}
47
+ {%- endif -%}
48
+ <html lang="{{ site.locale | slice: 0,2 | default: 'en' }}" class="no-js" data-bs-theme="{{ _bs_theme }}" data-color-mode-default="{{ _cm }}" data-theme-skin="{{ site.theme_skin | default: 'dark' }}" data-zer0-bg="{{ site.theme_background.enabled | default: true }}">
35
49
  <head>
36
50
  <!-- =============================================== -->
37
51
  <!-- HEAD SECTION: Meta tags, styles, and SEO setup -->