jekyll-theme-zer0 1.25.0 → 1.27.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 +201 -0
- data/README.md +11 -28
- data/_data/authors.yml +4 -3
- data/_data/backlog.yml +54 -1
- data/_data/features.yml +101 -61
- data/_data/feedback_types.yml +148 -0
- data/_data/i18n/languages.yml +36 -0
- data/_data/navigation/README.md +1 -1
- data/_data/theme-manifest.yml +0 -2
- data/_data/ui-text.yml +36 -246
- data/_includes/README.md +4 -0
- data/_includes/components/README.md +1 -0
- data/_includes/components/author-avatar-url.html +4 -2
- data/_includes/components/env-switcher.html +3 -1
- data/_includes/components/language-toggle.html +81 -0
- data/_includes/components/page-feedback.html +252 -0
- data/_includes/components/search-modal.html +2 -2
- data/_includes/components/shortcuts-modal.html +1 -1
- data/_includes/components/translation-notice.html +27 -0
- data/_includes/content/intro.html +35 -76
- data/_includes/core/console-capture.html +89 -0
- data/_includes/core/footer.html +9 -4
- data/_includes/core/head.html +13 -0
- data/_includes/core/header.html +9 -4
- data/_includes/core/hreflang.html +33 -0
- data/_includes/core/i18n.html +36 -0
- data/_includes/navigation/breadcrumbs.html +1 -1
- data/_includes/navigation/navbar.html +5 -4
- data/_includes/navigation/sidebar-right.html +3 -2
- data/_includes/navigation/unified-drawer.html +1 -1
- data/_layouts/article.html +7 -1
- data/_layouts/default.html +5 -3
- data/_layouts/news.html +4 -2
- data/_layouts/root.html +17 -8
- data/_layouts/section.html +4 -2
- data/_sass/components/_page-feedback.scss +276 -0
- data/_sass/core/_docs-layout.scss +2 -78
- data/_sass/core/_obsidian.scss +9 -1
- data/_sass/layouts/_navbar-extras.scss +6 -1
- data/_sass/tokens/_layers.scss +4 -0
- data/assets/css/main.scss +1 -0
- data/assets/js/obsidian-graph.js +5 -1
- data/assets/js/page-feedback.js +660 -0
- data/scripts/README.md +20 -26
- data/scripts/bin/audit-consumer +1 -1
- data/scripts/bin/manifest +0 -1
- data/scripts/bin/sync-plugins +0 -1
- data/scripts/dev/rasterize-svg.js +65 -0
- data/scripts/features/generate-preview-images +49 -1390
- data/scripts/features/install-preview-generator +55 -33
- data/scripts/install/README.md +9 -20
- data/scripts/install/ai/prompts/wizard.system.md +8 -17
- data/scripts/lib/README.md +1 -5
- data/scripts/lib/install/deploy/README.md +3 -9
- data/scripts/lib/preview_generator.py +2261 -1341
- data/scripts/translate.rb +1114 -0
- metadata +14 -4
- data/_data/prompts.yml +0 -302
- data/_plugins/preview_image_generator.rb +0 -351
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
PAGE FEEDBACK — "Improve this page" capture widget
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
Component: page-feedback
|
|
7
|
+
Path: _includes/components/page-feedback.html
|
|
8
|
+
Purpose: Turn what a reader is looking at into a well-formed GitHub
|
|
9
|
+
issue — request type + description + live page context +
|
|
10
|
+
captured console/error logs — filed with labels that exist.
|
|
11
|
+
Replaces the build-time Copilot dropdown that used to live in
|
|
12
|
+
content/intro.html and baked _data/prompts.yml into <a href>s.
|
|
13
|
+
|
|
14
|
+
Params: none — configured via site.page_feedback (+ site.data.feedback_types)
|
|
15
|
+
Depends on:
|
|
16
|
+
- assets/js/page-feedback.js (behaviour)
|
|
17
|
+
- _sass/components/_page-feedback.scss (styling)
|
|
18
|
+
- _includes/core/console-capture.html (the log buffer, installed in <head>)
|
|
19
|
+
- _data/feedback_types.yml (request-type taxonomy)
|
|
20
|
+
- FAB tokens (_sass/tokens/_spacing.scss, _layers.scss)
|
|
21
|
+
|
|
22
|
+
Layers:
|
|
23
|
+
- Base (always): opens a pre-filled github.com/issues/new form. No token,
|
|
24
|
+
works on any fork / GitHub Pages.
|
|
25
|
+
- AI enrichment (optional): when the chat proxy is deployed
|
|
26
|
+
(ai_chat.proxy_ready), an "Analyze with AI" step sends the capture to
|
|
27
|
+
Claude — via Claude Code OAuth, server-side — to clarify, vet,
|
|
28
|
+
prioritize, and recommend before the issue is filed.
|
|
29
|
+
|
|
30
|
+
Triggers: the global FAB below, plus any element with [data-pf-open]
|
|
31
|
+
elsewhere on the page (e.g. the button in content/intro.html).
|
|
32
|
+
Add data-pf-type="<id>" to preselect a request type.
|
|
33
|
+
|
|
34
|
+
a11y: role=dialog + aria-modal, labelled title, focus trap, Escape to
|
|
35
|
+
close, backdrop click to close, aria-live status region.
|
|
36
|
+
===================================================================
|
|
37
|
+
{%- endcomment -%}
|
|
38
|
+
|
|
39
|
+
{%- unless site.page_feedback.enabled -%}{%- comment -%} widget disabled {%- endcomment -%}
|
|
40
|
+
{%- else -%}
|
|
41
|
+
|
|
42
|
+
{%- comment -%}
|
|
43
|
+
Booleans aren't evaluated inside assign tags (Liquid stores a truthy string),
|
|
44
|
+
so compute render flags with if-tags. The AI layer needs the chat proxy
|
|
45
|
+
deployed — reuse ai_chat.proxy_ready rather than a second readiness flag.
|
|
46
|
+
{%- endcomment -%}
|
|
47
|
+
{%- assign pf_ai_available = false -%}
|
|
48
|
+
{%- if site.page_feedback.ai.enabled and site.ai_chat.proxy_ready -%}
|
|
49
|
+
{%- assign pf_ai_available = true -%}
|
|
50
|
+
{%- endif -%}
|
|
51
|
+
{%- assign pf_show_fab = false -%}
|
|
52
|
+
{%- if site.page_feedback.fab -%}{%- assign pf_show_fab = true -%}{%- endif -%}
|
|
53
|
+
|
|
54
|
+
{%- comment -%} Repo + file coordinates for the issue body (mirrors content/intro.html). {%- endcomment -%}
|
|
55
|
+
{%- assign pf_repo = site.repository | join: "" -%}
|
|
56
|
+
{%- assign pf_branch = site.branch | default: "main" -%}
|
|
57
|
+
{%- assign pf_source_url = "" -%}
|
|
58
|
+
{%- if pf_repo != "" and page.path -%}
|
|
59
|
+
{%- capture pf_source_url -%}https://github.com/{{ pf_repo }}/blob/{{ pf_branch }}/{{ page.path }}{%- endcapture -%}
|
|
60
|
+
{%- endif -%}
|
|
61
|
+
{%- assign pf_theme = site.theme | default: site.remote_theme | default: "" -%}
|
|
62
|
+
|
|
63
|
+
{%- if pf_show_fab -%}
|
|
64
|
+
<!-- Global feedback FAB (page-invariant; opens the modal) -->
|
|
65
|
+
<button type="button"
|
|
66
|
+
id="pageFeedbackFab"
|
|
67
|
+
class="pf-fab btn shadow-lg position-fixed"
|
|
68
|
+
data-pf-open
|
|
69
|
+
aria-haspopup="dialog"
|
|
70
|
+
aria-controls="pageFeedbackModal"
|
|
71
|
+
title="{{ site.page_feedback.fab_label | default: 'Improve this page' | escape }}"
|
|
72
|
+
aria-label="{{ site.page_feedback.fab_label | default: 'Improve this page' | escape }}">
|
|
73
|
+
<i class="bi {{ site.page_feedback.fab_icon | default: 'bi-megaphone' }}" aria-hidden="true"></i>
|
|
74
|
+
</button>
|
|
75
|
+
{%- endif -%}
|
|
76
|
+
|
|
77
|
+
<!-- Feedback modal (self-contained overlay; controlled by page-feedback.js).
|
|
78
|
+
`position-fixed` is required, not cosmetic: it opts this <body> child out of
|
|
79
|
+
the .zer0-bg-body flattening rule (_sass/theme/_backgrounds.scss) that would
|
|
80
|
+
otherwise force position:relative; z-index:1 and let the cookie banner steal
|
|
81
|
+
the dialog's clicks. -->
|
|
82
|
+
<div class="pf-overlay position-fixed" id="pageFeedbackModal" role="dialog" aria-modal="true"
|
|
83
|
+
aria-labelledby="pfTitle" aria-describedby="pfIntro" hidden>
|
|
84
|
+
<div class="pf-dialog shadow-lg" role="document">
|
|
85
|
+
|
|
86
|
+
<header class="pf-header">
|
|
87
|
+
<div class="pf-header-text">
|
|
88
|
+
<h2 id="pfTitle" class="pf-title h5 mb-1">
|
|
89
|
+
<i class="bi bi-megaphone me-2" aria-hidden="true"></i>Share feedback
|
|
90
|
+
</h2>
|
|
91
|
+
<p id="pfIntro" class="pf-intro text-muted mb-0">
|
|
92
|
+
Report a problem or suggest an improvement — it becomes a GitHub issue
|
|
93
|
+
with the page details attached.
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
<button type="button" class="pf-close btn btn-sm" id="pfClose" aria-label="Close feedback dialog">
|
|
97
|
+
<i class="bi bi-x-lg" aria-hidden="true"></i>
|
|
98
|
+
</button>
|
|
99
|
+
</header>
|
|
100
|
+
|
|
101
|
+
<form id="pfForm" class="pf-body" novalidate>
|
|
102
|
+
|
|
103
|
+
<!-- Step 1 — request type -->
|
|
104
|
+
<fieldset class="pf-field pf-types">
|
|
105
|
+
<legend class="pf-legend">What kind of feedback?</legend>
|
|
106
|
+
{%- assign pf_group = "" -%}
|
|
107
|
+
{%- for t in site.data.feedback_types -%}
|
|
108
|
+
{%- if t.group != pf_group -%}
|
|
109
|
+
{%- unless forloop.first -%}</div>{%- endunless -%}
|
|
110
|
+
<p class="pf-types-group">{{ t.group }}</p>
|
|
111
|
+
<div class="pf-types-grid" role="radiogroup" aria-label="{{ t.group | escape }}">
|
|
112
|
+
{%- assign pf_group = t.group -%}
|
|
113
|
+
{%- endif -%}
|
|
114
|
+
<button type="button"
|
|
115
|
+
class="pf-type"
|
|
116
|
+
role="radio"
|
|
117
|
+
aria-checked="false"
|
|
118
|
+
data-pf-type-id="{{ t.id }}"
|
|
119
|
+
tabindex="{% if forloop.first %}0{% else %}-1{% endif %}">
|
|
120
|
+
<i class="bi {{ t.icon | default: 'bi-chat-dots' }} pf-type-icon" aria-hidden="true"></i>
|
|
121
|
+
<span class="pf-type-text">
|
|
122
|
+
<span class="pf-type-label">{{ t.label }}</span>
|
|
123
|
+
{%- if t.description -%}<span class="pf-type-desc">{{ t.description }}</span>{%- endif -%}
|
|
124
|
+
</span>
|
|
125
|
+
</button>
|
|
126
|
+
{%- if forloop.last -%}</div>{%- endif -%}
|
|
127
|
+
{%- endfor -%}
|
|
128
|
+
</fieldset>
|
|
129
|
+
|
|
130
|
+
<!-- Step 2 — description -->
|
|
131
|
+
<div class="pf-field">
|
|
132
|
+
<label for="pfDescription" class="pf-label">Describe it</label>
|
|
133
|
+
<textarea id="pfDescription" class="form-control" rows="4"
|
|
134
|
+
placeholder="Pick a type above, then tell us what you have in mind."
|
|
135
|
+
aria-describedby="pfDescHelp" required></textarea>
|
|
136
|
+
<p id="pfDescHelp" class="pf-help text-muted">
|
|
137
|
+
Be specific — what you expected, what happened, or what would be better.
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Step 3 — what gets attached -->
|
|
142
|
+
<details class="pf-field pf-context" id="pfContextWrap">
|
|
143
|
+
<summary class="pf-summary">
|
|
144
|
+
<i class="bi bi-paperclip me-1" aria-hidden="true"></i>What gets attached
|
|
145
|
+
<span class="pf-context-count" id="pfContextCount"></span>
|
|
146
|
+
</summary>
|
|
147
|
+
<ul class="pf-context-list" id="pfContextList" aria-live="polite">
|
|
148
|
+
<!-- page title / url / path filled in by JS -->
|
|
149
|
+
</ul>
|
|
150
|
+
|
|
151
|
+
{%- if site.page_feedback.capture_logs -%}
|
|
152
|
+
<div class="pf-logs" id="pfLogsWrap" hidden>
|
|
153
|
+
<label class="pf-check">
|
|
154
|
+
<input type="checkbox" id="pfIncludeLogs" checked>
|
|
155
|
+
Include <span id="pfLogsCount">0</span> captured console/error line(s)
|
|
156
|
+
</label>
|
|
157
|
+
<pre class="pf-logs-preview" id="pfLogsPreview" tabindex="0" aria-label="Captured console logs"></pre>
|
|
158
|
+
<p class="pf-help text-muted mb-0">
|
|
159
|
+
<i class="bi bi-shield-check me-1" aria-hidden="true"></i>
|
|
160
|
+
These come from your browser only and are never sent until you submit.
|
|
161
|
+
Review them and untick the box to leave them out.
|
|
162
|
+
</p>
|
|
163
|
+
</div>
|
|
164
|
+
{%- endif -%}
|
|
165
|
+
</details>
|
|
166
|
+
|
|
167
|
+
{%- if pf_ai_available -%}
|
|
168
|
+
<!-- Step 4 — optional AI review (proxy + Claude Code OAuth) -->
|
|
169
|
+
<div class="pf-field pf-ai" id="pfAiWrap">
|
|
170
|
+
<div class="pf-ai-head">
|
|
171
|
+
<button type="button" class="btn btn-sm btn-outline-primary" id="pfAnalyze">
|
|
172
|
+
<i class="bi bi-robot me-1" aria-hidden="true"></i>Analyze with AI
|
|
173
|
+
</button>
|
|
174
|
+
<span class="pf-ai-hint text-muted">Optional — clarifies, prioritizes, and suggests labels.</span>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="pf-ai-result" id="pfAiResult" aria-live="polite" hidden></div>
|
|
177
|
+
</div>
|
|
178
|
+
{%- endif -%}
|
|
179
|
+
|
|
180
|
+
<p class="pf-status" id="pfStatus" role="status" aria-live="polite"></p>
|
|
181
|
+
|
|
182
|
+
</form>
|
|
183
|
+
|
|
184
|
+
<footer class="pf-footer">
|
|
185
|
+
<button type="button" class="btn btn-link btn-sm pf-copy" id="pfCopy">
|
|
186
|
+
<i class="bi bi-clipboard me-1" aria-hidden="true"></i>Copy details
|
|
187
|
+
</button>
|
|
188
|
+
<div class="pf-footer-primary">
|
|
189
|
+
<button type="button" class="btn btn-secondary btn-sm" id="pfCancel">Cancel</button>
|
|
190
|
+
<button type="submit" form="pfForm" class="btn btn-primary btn-sm" id="pfSubmit" disabled>
|
|
191
|
+
<i class="bi bi-github me-1" aria-hidden="true"></i>
|
|
192
|
+
<span class="pf-submit-label">Open GitHub issue</span>
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
</footer>
|
|
196
|
+
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Widget configuration consumed by assets/js/page-feedback.js -->
|
|
201
|
+
<script id="pageFeedbackConfig" type="application/json">
|
|
202
|
+
{
|
|
203
|
+
"enabled": true,
|
|
204
|
+
"mode": {{ site.page_feedback.mode | default: "url" | jsonify }},
|
|
205
|
+
"endpoint": {{ site.page_feedback.endpoint | default: "/api/feedback" | jsonify }},
|
|
206
|
+
"issueEndpoint": {{ site.page_feedback.issue_endpoint | default: "/api/github/issue" | jsonify }},
|
|
207
|
+
"repository": {{ pf_repo | jsonify }},
|
|
208
|
+
"baseBranch": {{ pf_branch | jsonify }},
|
|
209
|
+
"defaultLabels": {% if site.page_feedback.default_labels %}{{ site.page_feedback.default_labels | jsonify }}{% else %}[]{% endif %},
|
|
210
|
+
"assignee": {{ site.page_feedback.assignee | default: "" | jsonify }},
|
|
211
|
+
"captureLogs": {{ site.page_feedback.capture_logs | default: true | jsonify }},
|
|
212
|
+
"fabLabel": {{ site.page_feedback.fab_label | default: "Improve this page" | jsonify }},
|
|
213
|
+
"ai": {
|
|
214
|
+
"available": {{ pf_ai_available | jsonify }},
|
|
215
|
+
"auto": {{ site.page_feedback.ai.auto | default: false | jsonify }},
|
|
216
|
+
"model": {{ site.page_feedback.ai.model | default: "claude-opus-4-8" | jsonify }},
|
|
217
|
+
"maxTokens": {{ site.page_feedback.ai.max_tokens | default: 1024 }}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
</script>
|
|
221
|
+
|
|
222
|
+
<!-- Request-type taxonomy (from _data/feedback_types.yml) -->
|
|
223
|
+
<script id="pageFeedbackTypes" type="application/json">
|
|
224
|
+
{{ site.data.feedback_types | jsonify }}
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<!-- Live page context (build-time front matter; runtime detail added by JS) -->
|
|
228
|
+
<script id="pageFeedbackContext" type="application/json">
|
|
229
|
+
{
|
|
230
|
+
"title": {{ page.title | default: site.title | jsonify }},
|
|
231
|
+
"url": {{ page.url | absolute_url | jsonify }},
|
|
232
|
+
"path": {{ page.path | default: "" | jsonify }},
|
|
233
|
+
"sourceUrl": {{ pf_source_url | jsonify }},
|
|
234
|
+
"layout": {{ page.layout | default: "" | jsonify }},
|
|
235
|
+
"collection": {{ page.collection | default: "" | jsonify }},
|
|
236
|
+
"categories": {{ page.categories | default: "" | jsonify }},
|
|
237
|
+
"tags": {{ page.tags | default: "" | jsonify }},
|
|
238
|
+
"author": {{ page.author | default: site.author.name | default: "" | jsonify }},
|
|
239
|
+
"date": {{ page.date | date: "%Y-%m-%d" | default: "" | jsonify }},
|
|
240
|
+
"lastmod": {{ page.last_modified_at | default: page.lastmod | date: "%Y-%m-%d" | default: "" | jsonify }},
|
|
241
|
+
"branch": {{ pf_branch | jsonify }},
|
|
242
|
+
"repository": {{ pf_repo | jsonify }},
|
|
243
|
+
"siteUrl": {{ site.url | default: "" | jsonify }},
|
|
244
|
+
"baseurl": {{ site.baseurl | default: "" | jsonify }},
|
|
245
|
+
"theme": {{ pf_theme | jsonify }},
|
|
246
|
+
"jekyllEnv": {{ jekyll.environment | jsonify }}
|
|
247
|
+
}
|
|
248
|
+
</script>
|
|
249
|
+
|
|
250
|
+
<script defer src="{{ '/assets/js/page-feedback.js' | relative_url }}"></script>
|
|
251
|
+
|
|
252
|
+
{%- endunless -%}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
===================================================================
|
|
19
19
|
-->
|
|
20
20
|
|
|
21
|
-
{%-
|
|
22
|
-
{%- assign search_placeholder =
|
|
21
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
22
|
+
{%- assign search_placeholder = ui.search_placeholder_text | default: "Enter your search term..." -%}
|
|
23
23
|
{%- comment -%}
|
|
24
24
|
Existence-gate the form action to /sitemap/ only when that page is present
|
|
25
25
|
in the build (mirroring the footer Quick Links guard, and the section-sidebar
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
is a no-op when the element is absent.
|
|
15
15
|
===================================================================
|
|
16
16
|
{%- endcomment -%}
|
|
17
|
-
{%-
|
|
17
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
18
18
|
<div class="modal fade" id="zer0-shortcuts-modal" tabindex="-1"
|
|
19
19
|
aria-labelledby="zer0-shortcuts-modal-title" aria-hidden="true">
|
|
20
20
|
<div class="modal-dialog modal-dialog-centered">
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{% comment %} Feature: ZER0-078 {% endcomment %}
|
|
2
|
+
{%- comment -%}
|
|
3
|
+
Component: translation-notice
|
|
4
|
+
Path: _includes/components/translation-notice.html
|
|
5
|
+
Purpose: Disclosure banner shown at the top of machine-generated
|
|
6
|
+
translations: tells the reader the page was translated
|
|
7
|
+
automatically and links back to the English original.
|
|
8
|
+
Params: none — renders only when page.machine_translated is true
|
|
9
|
+
(set by scripts/translate.rb on generated pages).
|
|
10
|
+
Depends on: core/i18n.html, Bootstrap 5 alert, Bootstrap Icons
|
|
11
|
+
Notes: No-op on every human-authored page, so it is safe to include
|
|
12
|
+
unconditionally from the root layout.
|
|
13
|
+
{%- endcomment -%}
|
|
14
|
+
{%- if page.machine_translated -%}
|
|
15
|
+
{%- include core/i18n.html -%}
|
|
16
|
+
<div class="container-xl mt-3 translation-notice">
|
|
17
|
+
<div class="alert alert-info d-flex align-items-start gap-2 mb-0" role="note">
|
|
18
|
+
<i class="bi bi-translate flex-shrink-0 mt-1" aria-hidden="true"></i>
|
|
19
|
+
<span>
|
|
20
|
+
{{ ui.machine_translation_notice | default: "This page was translated automatically from the English original." }}
|
|
21
|
+
{%- if page.translation_source_url %}
|
|
22
|
+
<a class="alert-link" href="{{ page.translation_source_url | relative_url }}" hreflang="{{ site.translation.source_lang | default: 'en' }}">{{ ui.machine_translation_view_source | default: "View the original" }}</a>
|
|
23
|
+
{%- endif %}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
{%- endif -%}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
file: _includes/content/intro.html
|
|
3
|
-
description: Enhanced intro section with share, edit, and
|
|
3
|
+
description: Enhanced intro section with share, edit, and improve/report buttons
|
|
4
4
|
path: _includes/content/intro.html
|
|
5
5
|
features:
|
|
6
6
|
- Responsive share dropdown with multiple social platforms
|
|
7
7
|
- Edit on GitHub quick link
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
- "Improve" button — opens the page-feedback widget (capture type + description
|
|
9
|
+
+ live page context + console logs → GitHub issue). Progressive enhancement:
|
|
10
|
+
it is a real link to the issue form, upgraded by assets/js/page-feedback.js.
|
|
11
|
+
See _includes/components/page-feedback.html.
|
|
11
12
|
- Responsive flex layout: full-width content block, frosted metadata footer with reading time and action buttons
|
|
12
13
|
- Balanced padding and typography hierarchy via bd-intro-* classes
|
|
13
14
|
- Metadata row: author, dates, category, difficulty, tags, view source
|
|
14
|
-
-->
|
|
15
|
+
-->
|
|
16
|
+
{%- include core/i18n.html -%}
|
|
15
17
|
{% if page.collection %}
|
|
16
18
|
{% capture page_dir %}{{ site.collections_dir }}{% endcapture %}
|
|
17
19
|
{% assign testvar = "collection exists" %}
|
|
@@ -132,7 +134,7 @@
|
|
|
132
134
|
{% endif %}
|
|
133
135
|
|
|
134
136
|
<div class="bd-intro" style="
|
|
135
|
-
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path
|
|
137
|
+
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('{{ preview_path }}') no-repeat center center / cover;">
|
|
136
138
|
|
|
137
139
|
<div class="bd-intro-inner">
|
|
138
140
|
<div class="bd-intro-content">
|
|
@@ -160,7 +162,7 @@
|
|
|
160
162
|
<li class="bd-intro-meta-item">
|
|
161
163
|
<i class="bi bi-calendar3 bd-intro-meta-icon" aria-hidden="true"></i>
|
|
162
164
|
<time datetime="{{ intro_published | date_to_xmlschema }}" itemprop="datePublished">
|
|
163
|
-
Published {{ intro_published | date: "%b %d, %Y" }}
|
|
165
|
+
{{ ui.published_label | default: "Published" }} {{ intro_published | date: "%b %d, %Y" }}
|
|
164
166
|
</time>
|
|
165
167
|
</li>
|
|
166
168
|
{% endif %}
|
|
@@ -169,7 +171,7 @@
|
|
|
169
171
|
<li class="bd-intro-meta-item">
|
|
170
172
|
<i class="bi bi-pencil bd-intro-meta-icon" aria-hidden="true"></i>
|
|
171
173
|
<time datetime="{{ intro_updated | date_to_xmlschema }}" itemprop="dateModified">
|
|
172
|
-
Updated {{ intro_updated | date: "%b %d, %Y" }}
|
|
174
|
+
{{ ui.updated_label | default: "Updated" }} {{ intro_updated | date: "%b %d, %Y" }}
|
|
173
175
|
</time>
|
|
174
176
|
</li>
|
|
175
177
|
{% endif %}
|
|
@@ -211,7 +213,7 @@
|
|
|
211
213
|
{% if intro_source_url %}
|
|
212
214
|
<li class="bd-intro-meta-item bd-intro-meta-item--source d-none d-md-inline-flex">
|
|
213
215
|
<i class="bi bi-file-earmark-code bd-intro-meta-icon" aria-hidden="true"></i>
|
|
214
|
-
<a class="bd-intro-meta-link" href="{{ intro_source_url }}" target="_blank" rel="noopener noreferrer">View source</a>
|
|
216
|
+
<a class="bd-intro-meta-link" href="{{ intro_source_url }}" target="_blank" rel="noopener noreferrer">{{ ui.view_source | default: "View source" }}</a>
|
|
215
217
|
</li>
|
|
216
218
|
{% endif %}
|
|
217
219
|
</ul>
|
|
@@ -220,9 +222,9 @@
|
|
|
220
222
|
<p class="bd-intro-meta">
|
|
221
223
|
<i class="bi bi-clock me-1" aria-hidden="true"></i>
|
|
222
224
|
{% if page.estimated_reading_time %}
|
|
223
|
-
Estimated reading time: {{ page.estimated_reading_time }}
|
|
225
|
+
{{ ui.reading_time_label | default: "Estimated reading time:" }} {{ page.estimated_reading_time }}
|
|
224
226
|
{% else %}
|
|
225
|
-
Estimated reading time: {{ intro_read_mins }} minute{% if intro_read_mins != 1 %}s{% endif %}
|
|
227
|
+
{{ ui.reading_time_label | default: "Estimated reading time:" }} {{ intro_read_mins }} {{ ui.reading_time_minute | default: "minute" }}{% if intro_read_mins != 1 %}s{% endif %}
|
|
226
228
|
{% endif %}
|
|
227
229
|
</p>
|
|
228
230
|
|
|
@@ -232,12 +234,12 @@
|
|
|
232
234
|
<div class="dropdown">
|
|
233
235
|
<button class="btn btn-sm btn-info dropdown-toggle" type="button" id="shareDropdownBottom" data-bs-toggle="dropdown" aria-expanded="false">
|
|
234
236
|
<i class="bi bi-share" aria-hidden="true"></i>
|
|
235
|
-
<span class="d-none d-sm-inline ms-1">Share</span>
|
|
237
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.share_label | default: "Share" }}</span>
|
|
236
238
|
</button>
|
|
237
239
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="shareDropdownBottom">
|
|
238
240
|
<li>
|
|
239
241
|
<a class="dropdown-item" href="https://reddit.com/submit?url={{ page_absolute_url | uri_escape }}&title={{ page.title | uri_escape }}" target="_blank" rel="noopener noreferrer">
|
|
240
|
-
<i class="bi bi-reddit me-2"></i>Share on Reddit
|
|
242
|
+
<i class="bi bi-reddit me-2"></i>{{ ui.share_on_label | default: "Share on" }} Reddit
|
|
241
243
|
</a>
|
|
242
244
|
</li>
|
|
243
245
|
<li>
|
|
@@ -249,12 +251,12 @@
|
|
|
249
251
|
data-share-url="{{ page_absolute_url | escape }}"
|
|
250
252
|
data-share-title="{{ linkedin_share_title | escape }}"
|
|
251
253
|
data-share-description="{{ linkedin_share_description | escape }}">
|
|
252
|
-
<i class="bi bi-linkedin me-2"></i>Share on LinkedIn
|
|
254
|
+
<i class="bi bi-linkedin me-2"></i>{{ ui.share_on_label | default: "Share on" }} LinkedIn
|
|
253
255
|
</a>
|
|
254
256
|
</li>
|
|
255
257
|
<li>
|
|
256
|
-
<a class="dropdown-item" href="https://x.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank" aria-label="Share on X">
|
|
257
|
-
<i class="bi bi-twitter-x me-2"></i>Share on X
|
|
258
|
+
<a class="dropdown-item" href="https://x.com/intent/tweet?url={{ site.url | append: page.url | url_encode }}&text={{ page.title | url_encode }}" target="_blank" aria-label="{{ ui.share_on_label | default: 'Share on' }} X">
|
|
259
|
+
<i class="bi bi-twitter-x me-2"></i>{{ ui.share_on_label | default: "Share on" }} X
|
|
258
260
|
</a>
|
|
259
261
|
</li>
|
|
260
262
|
<li><hr class="dropdown-divider"></li>
|
|
@@ -265,70 +267,27 @@
|
|
|
265
267
|
the blocking alert() the legacy markup used.
|
|
266
268
|
{%- endcomment -%}
|
|
267
269
|
<button class="dropdown-item" type="button" data-copy="{{ site.url | append: page.url }}">
|
|
268
|
-
<i class="bi bi-clipboard me-2" aria-hidden="true"></i>Copy Link
|
|
270
|
+
<i class="bi bi-clipboard me-2" aria-hidden="true"></i>{{ ui.copy_link_label | default: "Copy Link" }}
|
|
269
271
|
</button>
|
|
270
272
|
</li>
|
|
271
273
|
</ul>
|
|
272
274
|
</div>
|
|
273
275
|
|
|
274
|
-
<!--
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
{% capture issue_body %}{{ prompt.body }}
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
## 📄 Page Context
|
|
293
|
-
|
|
294
|
-
| Field | Value |
|
|
295
|
-
|---|---|
|
|
296
|
-
| **Title** | {{ page.title }} |
|
|
297
|
-
| **URL** | {{ site.url }}{{ page.url }} |
|
|
298
|
-
| **File** | `{{ file_path }}` |
|
|
299
|
-
| **Branch** | `{{ repo_branch }}` |
|
|
300
|
-
| **Layout** | `{{ page.layout }}` |
|
|
301
|
-
| **Collection** | `{{ page.collection }}` |
|
|
302
|
-
| **Author** | {{ page.author | default: site.author.name }} |
|
|
303
|
-
| **Date** | {{ page.date }} |
|
|
304
|
-
| **Last Modified** | {{ page.lastmod }} |
|
|
305
|
-
| **Tags** | {{ page.tags | join: ", " }} |
|
|
306
|
-
| **Categories** | {{ page.categories | join: ", " }} |
|
|
307
|
-
|
|
308
|
-
## 🔧 Environment
|
|
309
|
-
|
|
310
|
-
| Field | Value |
|
|
311
|
-
|---|---|
|
|
312
|
-
| **Repository** | `{{ site.repository }}` |
|
|
313
|
-
| **Site URL** | {{ site.url }} |
|
|
314
|
-
| **Jekyll Env** | `{{ jekyll.environment }}` |
|
|
315
|
-
| **Theme** | `{{ site.theme | default: site.remote_theme }}` |
|
|
316
|
-
| **Base URL** | `{{ site.baseurl }}` |
|
|
317
|
-
{% endcapture %}
|
|
318
|
-
<li>
|
|
319
|
-
<a class="dropdown-item" href="https://github.com/{{ site.repository }}/issues/new?assignees=copilot&labels=ai-agent&title={{ issue_title | url_encode }}&body={{ issue_body | url_encode }}" target="_blank" rel="noopener noreferrer">
|
|
320
|
-
<span class="copilot-agent-item">
|
|
321
|
-
<i class="bi {{ prompt.icon }} copilot-agent-item__icon" aria-hidden="true"></i>
|
|
322
|
-
<span class="copilot-agent-item__text">
|
|
323
|
-
<span class="copilot-agent-item__label">{{ prompt.label }}</span>
|
|
324
|
-
{% if prompt.description %}<small class="copilot-agent-item__desc">{{ prompt.description }}</small>{% endif %}
|
|
325
|
-
</span>
|
|
326
|
-
</span>
|
|
327
|
-
</a>
|
|
328
|
-
</li>
|
|
329
|
-
{% endfor %}
|
|
330
|
-
</ul>
|
|
331
|
-
</div>
|
|
276
|
+
<!-- Improve / Report — opens the page-feedback widget (assets/js/page-feedback.js).
|
|
277
|
+
Progressive enhancement: this is a real link to the issue form, so it
|
|
278
|
+
works without JavaScript; page-feedback.js intercepts [data-pf-open]
|
|
279
|
+
clicks and opens the richer capture modal (context + logs + AI) instead. -->
|
|
280
|
+
{% if site.page_feedback.enabled %}
|
|
281
|
+
<a class="btn btn-sm btn-success bd-intro-action-link"
|
|
282
|
+
href="https://github.com/{{ site.repository }}/issues/new?template=page_improvement.yml&labels=page-feedback"
|
|
283
|
+
data-pf-open
|
|
284
|
+
target="_blank"
|
|
285
|
+
rel="noopener noreferrer"
|
|
286
|
+
title="Report a problem or suggest an improvement for this page">
|
|
287
|
+
<i class="bi bi-megaphone" aria-hidden="true"></i>
|
|
288
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.improve_label | default: "Improve" }}</span>
|
|
289
|
+
</a>
|
|
290
|
+
{% endif %}
|
|
332
291
|
|
|
333
292
|
<!-- Edit on GitHub Button -->
|
|
334
293
|
<a href="https://github.com/{{ site.repository }}/blob/{{ repo_branch }}/{{ file_path }}"
|
|
@@ -336,7 +295,7 @@
|
|
|
336
295
|
target="_blank"
|
|
337
296
|
rel="noopener noreferrer">
|
|
338
297
|
<i class="bi bi-github" aria-hidden="true"></i>
|
|
339
|
-
<span class="d-none d-sm-inline ms-1">Edit on GitHub</span>
|
|
298
|
+
<span class="d-none d-sm-inline ms-1">{{ ui.edit_on_github | default: "Edit on GitHub" }}</span>
|
|
340
299
|
</a>
|
|
341
300
|
</div>
|
|
342
301
|
</div>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
===================================================================
|
|
3
|
+
CONSOLE CAPTURE — early, tiny console/error ring buffer
|
|
4
|
+
===================================================================
|
|
5
|
+
|
|
6
|
+
File: console-capture.html
|
|
7
|
+
Path: _includes/core/console-capture.html
|
|
8
|
+
Purpose: Capture a capped, in-memory buffer of console output and
|
|
9
|
+
uncaught errors, installed HIGH in <head> — before the
|
|
10
|
+
theme's deferred scripts run — so the page-feedback widget
|
|
11
|
+
(assets/js/page-feedback.js) can attach the logs and JS
|
|
12
|
+
errors that led up to a report.
|
|
13
|
+
|
|
14
|
+
Renders: only when page_feedback.enabled AND page_feedback.capture_logs.
|
|
15
|
+
|
|
16
|
+
Privacy: in-memory only, never persisted, never sent anywhere until the
|
|
17
|
+
user opens the feedback modal, reviews the captured logs, and
|
|
18
|
+
chooses to submit. Any single entry is truncated; the buffer is
|
|
19
|
+
a fixed-size ring (page_feedback.log_limit).
|
|
20
|
+
|
|
21
|
+
Depends on: nothing — pure inline ES5, GitHub Pages-safe (no CDN).
|
|
22
|
+
===================================================================
|
|
23
|
+
{%- endcomment -%}
|
|
24
|
+
{%- if site.page_feedback.enabled and site.page_feedback.capture_logs -%}
|
|
25
|
+
<script>
|
|
26
|
+
/* page-feedback: early console/error capture — see _includes/core/console-capture.html */
|
|
27
|
+
(function () {
|
|
28
|
+
"use strict";
|
|
29
|
+
if (window.__zer0Feedback && window.__zer0Feedback.installed) { return; }
|
|
30
|
+
|
|
31
|
+
var LIMIT = {{ site.page_feedback.log_limit | default: 40 | jsonify }};
|
|
32
|
+
var MAX_ENTRY = 600; // truncate any single entry so the buffer stays small
|
|
33
|
+
var start = (window.performance && performance.now) ? null : Date.now();
|
|
34
|
+
|
|
35
|
+
var buffer = [];
|
|
36
|
+
function now() { return start === null ? Math.round(performance.now()) : (Date.now() - start); }
|
|
37
|
+
|
|
38
|
+
function stringify(value) {
|
|
39
|
+
if (typeof value === "string") { return value; }
|
|
40
|
+
if (value instanceof Error) {
|
|
41
|
+
return (value.name || "Error") + ": " + (value.message || "") + (value.stack ? "\n" + value.stack : "");
|
|
42
|
+
}
|
|
43
|
+
try { return JSON.stringify(value); } catch (e) { return String(value); }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function push(level, args) {
|
|
47
|
+
try {
|
|
48
|
+
var parts = [];
|
|
49
|
+
for (var i = 0; i < args.length; i++) { parts.push(stringify(args[i])); }
|
|
50
|
+
var text = parts.join(" ");
|
|
51
|
+
if (text.length > MAX_ENTRY) { text = text.slice(0, MAX_ENTRY) + "…"; }
|
|
52
|
+
buffer.push({ t: now(), level: level, text: text });
|
|
53
|
+
while (buffer.length > LIMIT) { buffer.shift(); }
|
|
54
|
+
} catch (e) { /* logging must never break the page */ }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Wrap console methods, preserving passthrough to the real console.
|
|
58
|
+
if (window.console) {
|
|
59
|
+
["log", "info", "warn", "error", "debug"].forEach(function (level) {
|
|
60
|
+
var original = console[level] ? console[level].bind(console) : null;
|
|
61
|
+
console[level] = function () {
|
|
62
|
+
push(level, arguments);
|
|
63
|
+
if (original) { original.apply(null, arguments); }
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Uncaught errors and unhandled promise rejections.
|
|
69
|
+
window.addEventListener("error", function (e) {
|
|
70
|
+
if (!e) { return; }
|
|
71
|
+
var where = e.filename ? " (" + e.filename + ":" + (e.lineno || 0) + ":" + (e.colno || 0) + ")" : "";
|
|
72
|
+
push("error", [(e.message || "Script error") + where]);
|
|
73
|
+
});
|
|
74
|
+
window.addEventListener("unhandledrejection", function (e) {
|
|
75
|
+
var reason = e && e.reason;
|
|
76
|
+
var msg = reason && reason.message ? reason.message : stringify(reason);
|
|
77
|
+
push("error", ["Unhandled promise rejection: " + msg]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
window.__zer0Feedback = {
|
|
81
|
+
installed: true,
|
|
82
|
+
logs: buffer,
|
|
83
|
+
/** Snapshot the buffer (newest last) for the widget to read. */
|
|
84
|
+
snapshot: function () { return buffer.slice(); },
|
|
85
|
+
clear: function () { buffer.length = 0; }
|
|
86
|
+
};
|
|
87
|
+
})();
|
|
88
|
+
</script>
|
|
89
|
+
{%- endif -%}
|
data/_includes/core/footer.html
CHANGED
|
@@ -22,15 +22,20 @@
|
|
|
22
22
|
- site.powered_by: Framework and service credits
|
|
23
23
|
- site.links: Social media and affiliate links
|
|
24
24
|
- site.default_icon: Icon framework (Bootstrap Icons)
|
|
25
|
-
-
|
|
25
|
+
- core/i18n.html: UI strings (per-page language via the `lang` param)
|
|
26
26
|
- Bootstrap 5 classes provided by the theme
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
Params:
|
|
29
|
+
- lang (optional): page language. Root layout renders this include via
|
|
30
|
+
include_cached, so it MUST pass lang=page.lang for per-language caching.
|
|
31
|
+
|
|
28
32
|
Notes:
|
|
29
33
|
- Guard optional fields (address/phone/email) to avoid empty output
|
|
30
34
|
- Use relative_url for internal links to respect baseurl
|
|
31
35
|
===================================================================
|
|
32
36
|
-->
|
|
33
37
|
|
|
38
|
+
{%- include core/i18n.html lang=include.lang -%}
|
|
34
39
|
<footer class="bd-footer border-top" role="contentinfo">
|
|
35
40
|
{% assign footer_page_urls = site.html_pages | map: "url" | join: "|" | prepend: "|" | append: "|" %}
|
|
36
41
|
<!-- Powered by Row -->
|
|
@@ -40,7 +45,7 @@
|
|
|
40
45
|
</p>
|
|
41
46
|
<div class="footer-powered-by-row">
|
|
42
47
|
<span class="footer-powered-by-label" id="footer-powered-by-label">
|
|
43
|
-
{{
|
|
48
|
+
{{ ui.powered_by | default: "Powered by" }}:
|
|
44
49
|
</span>
|
|
45
50
|
<ul class="footer-powered-by-links list-unstyled mb-0"
|
|
46
51
|
aria-labelledby="footer-powered-by-label">
|
|
@@ -197,7 +202,7 @@
|
|
|
197
202
|
<li class="list-inline-item">
|
|
198
203
|
<a class="text-light text-decoration-none" href="{{ '/feed.xml' | relative_url }}">
|
|
199
204
|
<i class="{{ site.default_icon }} bi-rss" aria-hidden="true"></i>
|
|
200
|
-
<span class="d-none d-md-inline">{{
|
|
205
|
+
<span class="d-none d-md-inline">{{ ui.feed_label | default: "Feed" }}</span>
|
|
201
206
|
</a>
|
|
202
207
|
</li>
|
|
203
208
|
</ul>
|