datalog-theme 0.6.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 +7 -0
- data/CHANGELOG.md +162 -0
- data/CITATION.cff +29 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/_data/academic.yml +217 -0
- data/_data/cdn-integrity.yml +51 -0
- data/_data/config/author.yml +121 -0
- data/_data/config/features.yml +262 -0
- data/_data/config/site.yml +42 -0
- data/_data/config/theme.yml +181 -0
- data/_data/datasets.yml +28 -0
- data/_data/i18n/en.yml +438 -0
- data/_data/i18n/es.yml +438 -0
- data/_data/i18n/pt.yml +438 -0
- data/_data/js_manifest.json +22 -0
- data/_data/js_meta.json +371 -0
- data/_data/navigation.yml +145 -0
- data/_data/projects.yml +41 -0
- data/_data/publications.yml +28 -0
- data/_data/social.yml +73 -0
- data/_data/visualizations.yml +51 -0
- data/_includes/analytics/dashboard.html +149 -0
- data/_includes/analytics.html +9 -0
- data/_includes/components/academic-dashboard.html +347 -0
- data/_includes/components/advanced-search.html +682 -0
- data/_includes/components/api-function.html +94 -0
- data/_includes/components/author-bio.html +166 -0
- data/_includes/components/bookmark-system.html +96 -0
- data/_includes/components/breadcrumbs.html +89 -0
- data/_includes/components/citation-tools.html +94 -0
- data/_includes/components/comments.html +244 -0
- data/_includes/components/content-provenance.html +43 -0
- data/_includes/components/content-recommendations.html +228 -0
- data/_includes/components/difficulty-badge.html +84 -0
- data/_includes/components/email-preferences.html +200 -0
- data/_includes/components/enhanced-code-block.html +212 -0
- data/_includes/components/enhanced-metadata.html +228 -0
- data/_includes/components/enhanced-toc.html +209 -0
- data/_includes/components/hero.html +15 -0
- data/_includes/components/language-switcher.html +396 -0
- data/_includes/components/math-fallback.html +19 -0
- data/_includes/components/navigation-enhancements.html +454 -0
- data/_includes/components/newsletter-signup.html +178 -0
- data/_includes/components/open-science-badges.html +16 -0
- data/_includes/components/package-install.html +194 -0
- data/_includes/components/performance-monitor.html +170 -0
- data/_includes/components/popular-posts.html +233 -0
- data/_includes/components/post-hero.html +62 -0
- data/_includes/components/reading-progress.html +133 -0
- data/_includes/components/reading-time.html +121 -0
- data/_includes/components/responsive-image.html +63 -0
- data/_includes/components/search-facets.html +307 -0
- data/_includes/components/series-navigation.html +124 -0
- data/_includes/components/social-proof.html +34 -0
- data/_includes/components/social-share.html +119 -0
- data/_includes/components/user-preferences.html +566 -0
- data/_includes/components/visualization-card.html +24 -0
- data/_includes/components/viz-table-fallback.html +19 -0
- data/_includes/critical-css/default.html +0 -0
- data/_includes/critical-css/home.html +0 -0
- data/_includes/critical-css/post.html +0 -0
- data/_includes/csp-meta.html +17 -0
- data/_includes/footer/nav-column.html +31 -0
- data/_includes/footer.html +76 -0
- data/_includes/head.html +337 -0
- data/_includes/header/navigation.html +48 -0
- data/_includes/header.html +70 -0
- data/_includes/helpers/array-to-sentence.html +21 -0
- data/_includes/helpers/date-format.html +16 -0
- data/_includes/helpers/link-with-icon.html +26 -0
- data/_includes/layouts/default/article.html +67 -0
- data/_includes/meta/language-attributes.html +25 -0
- data/_includes/meta/math-config.html +21 -0
- data/_includes/meta/schema.html +153 -0
- data/_includes/meta/scripts-loader.html +52 -0
- data/_includes/post/related-posts.html +63 -0
- data/_includes/scripts.html +72 -0
- data/_includes/skip-link.html +1 -0
- data/_includes/toc.html +2 -0
- data/_layouts/archive.html +282 -0
- data/_layouts/dataset.html +52 -0
- data/_layouts/default.html +33 -0
- data/_layouts/home.html +48 -0
- data/_layouts/notebook.html +173 -0
- data/_layouts/package.html +238 -0
- data/_layouts/page.html +16 -0
- data/_layouts/portfolio.html +41 -0
- data/_layouts/post-sidebar.html +183 -0
- data/_layouts/post.html +338 -0
- data/_layouts/project.html +252 -0
- data/_layouts/research.html +504 -0
- data/_plugins/analytics_dashboard.rb +315 -0
- data/_plugins/config_validator.rb +397 -0
- data/_plugins/csp_generator.rb +115 -0
- data/_plugins/datalog_bibliography.rb +19 -0
- data/_plugins/datalog_comments.rb +18 -0
- data/_plugins/datalog_slides.rb +20 -0
- data/_plugins/front_matter_compat.rb +109 -0
- data/_plugins/i18n.rb +160 -0
- data/_plugins/image_optimizer.rb +445 -0
- data/_plugins/math_preprocessor.rb +185 -0
- data/_plugins/notebook_converter.rb +1107 -0
- data/_plugins/plugin_loader.rb +185 -0
- data/_plugins/publications_generator.rb +234 -0
- data/_plugins/reading_time.rb +15 -0
- data/_plugins/search_normalizer.rb +74 -0
- data/_plugins/sri_filter.rb +18 -0
- data/_plugins/toc_filter.rb +68 -0
- data/_plugins/warning_filter.rb +3 -0
- data/_sass/_base.scss +142 -0
- data/_sass/_components.scss +2380 -0
- data/_sass/_header.scss +470 -0
- data/_sass/_interactive.scss +344 -0
- data/_sass/_layout.scss +1760 -0
- data/_sass/_mathematical.scss +368 -0
- data/_sass/_mixins.scss +99 -0
- data/_sass/_package-docs.scss +715 -0
- data/_sass/_phase1-enhancements.scss +721 -0
- data/_sass/_phase3-enhancements.scss +874 -0
- data/_sass/_phase4-enhancements.scss +1214 -0
- data/_sass/_phase5-enhancements.scss +414 -0
- data/_sass/_search.scss +654 -0
- data/_sass/_syntax-highlighting.scss +128 -0
- data/_sass/_theme.scss +79 -0
- data/_sass/_typography.scss +148 -0
- data/_sass/_utilities.scss +334 -0
- data/_sass/_variables.scss +153 -0
- data/_sass/_visualizations.scss +242 -0
- data/assets/css/main.scss +4 -0
- data/assets/img/20220607123041_detail.001.png +0 -0
- data/assets/img/favicons/android-chrome-192x192.png +0 -0
- data/assets/img/favicons/android-chrome-512x512.png +0 -0
- data/assets/img/favicons/apple-touch-icon.png +0 -0
- data/assets/img/favicons/favicon-16x16.png +0 -0
- data/assets/img/favicons/favicon-32x32.png +0 -0
- data/assets/img/favicons/favicon.ico +0 -0
- data/assets/img/favicons/site.webmanifest +21 -0
- data/assets/img/portfolio-placeholder.svg +20 -0
- data/assets/js/academic.js +262 -0
- data/assets/js/analytics-dashboard.js +382 -0
- data/assets/js/core/dark-mode.js +79 -0
- data/assets/js/core/github-cards.js +123 -0
- data/assets/js/core/language-filter.js +69 -0
- data/assets/js/core/navigation.js +184 -0
- data/assets/js/core/scroll-progress.js +45 -0
- data/assets/js/core/search-hotkeys.js +62 -0
- data/assets/js/core/skip-links.js +62 -0
- data/assets/js/loader.js +163 -0
- data/assets/js/main.js +23 -0
- data/assets/js/math.js +818 -0
- data/assets/js/notebook.js +158 -0
- data/assets/js/search/analytics.js +91 -0
- data/assets/js/search/app.js +271 -0
- data/assets/js/search/autocomplete.js +120 -0
- data/assets/js/search/engine.js +260 -0
- data/assets/js/search/filters.js +38 -0
- data/assets/js/search/render.js +217 -0
- data/assets/js/search/utils.js +99 -0
- data/assets/js/search.js +354 -0
- data/assets/js/visualizations.js +816 -0
- data/assets/publications/datalog-publications.bib +8 -0
- data/assets/publications/datalog-publications.ris +9 -0
- data/assets/publications/publications.bib +30 -0
- data/assets/templates/diogo-ribeiro-cv.md +31 -0
- data/assets/templates/diogo-ribeiro-cv.tex +32 -0
- data/bin/datalog +26 -0
- data/datalog-theme.gemspec +72 -0
- data/lib/datalog/cli.rb +563 -0
- data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
- data/lib/datalog/plugin_system.rb +237 -0
- data/lib/datalog/plugins/citations.rb +182 -0
- data/lib/datalog/plugins/comments.rb +183 -0
- data/lib/datalog/plugins/search.rb +45 -0
- data/lib/datalog/plugins/slides.rb +121 -0
- data/lib/datalog/theme/theme.rb +18 -0
- data/lib/datalog/theme/version.rb +7 -0
- data/lib/datalog/warning_filter.rb +28 -0
- data/lib/datalog-theme.rb +17 -0
- metadata +555 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Enhanced table of contents with:
|
|
3
|
+
- Sticky positioning
|
|
4
|
+
- Progress indicator
|
|
5
|
+
- Collapsible sections
|
|
6
|
+
- Active section highlighting
|
|
7
|
+
- Scroll spy functionality
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
- html: Content to parse (default: content)
|
|
11
|
+
- h_min: Minimum heading level (default: 2)
|
|
12
|
+
- h_max: Maximum heading level (default: 4)
|
|
13
|
+
- heading: TOC title (default: "Table of Contents")
|
|
14
|
+
- collapsible: Make TOC collapsible (default: true)
|
|
15
|
+
- show_progress: Show reading progress bar (default: true)
|
|
16
|
+
{%- endcomment -%}
|
|
17
|
+
|
|
18
|
+
{%- assign toc_content = include.html | default: content -%}
|
|
19
|
+
{%- assign h_min = include.h_min | default: 2 -%}
|
|
20
|
+
{%- assign h_max = include.h_max | default: 4 -%}
|
|
21
|
+
{%- assign toc_heading = include.heading | default: "Table of Contents" -%}
|
|
22
|
+
{%- assign collapsible = include.collapsible | default: true -%}
|
|
23
|
+
{%- assign show_progress = include.show_progress | default: true -%}
|
|
24
|
+
|
|
25
|
+
{%- capture toc_markup -%}
|
|
26
|
+
{{ toc_content | markdownify | tocify: h_min, h_max }}
|
|
27
|
+
{%- endcapture -%}
|
|
28
|
+
|
|
29
|
+
{%- if toc_markup contains '<li' -%}
|
|
30
|
+
<aside class="enhanced-toc{% if collapsible %} enhanced-toc--collapsible{% endif %}"
|
|
31
|
+
data-toc-enhanced
|
|
32
|
+
aria-label="{{ toc_heading }}"
|
|
33
|
+
role="doc-toc">
|
|
34
|
+
|
|
35
|
+
{%- if show_progress -%}
|
|
36
|
+
<div class="enhanced-toc__progress" aria-hidden="true">
|
|
37
|
+
<div class="enhanced-toc__progress-bar" data-toc-progress></div>
|
|
38
|
+
</div>
|
|
39
|
+
{%- endif -%}
|
|
40
|
+
|
|
41
|
+
<div class="enhanced-toc__header">
|
|
42
|
+
<h2 class="enhanced-toc__heading">{{ toc_heading }}</h2>
|
|
43
|
+
{%- if collapsible -%}
|
|
44
|
+
<button type="button"
|
|
45
|
+
class="enhanced-toc__toggle"
|
|
46
|
+
aria-expanded="true"
|
|
47
|
+
aria-controls="toc-content"
|
|
48
|
+
data-toc-toggle>
|
|
49
|
+
<svg class="enhanced-toc__toggle-icon enhanced-toc__toggle-icon--expand"
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
+
width="20"
|
|
52
|
+
height="20"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="none"
|
|
55
|
+
stroke="currentColor"
|
|
56
|
+
stroke-width="2"
|
|
57
|
+
stroke-linecap="round"
|
|
58
|
+
stroke-linejoin="round"
|
|
59
|
+
aria-hidden="true">
|
|
60
|
+
<polyline points="18 15 12 9 6 15"></polyline>
|
|
61
|
+
</svg>
|
|
62
|
+
<svg class="enhanced-toc__toggle-icon enhanced-toc__toggle-icon--collapse"
|
|
63
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
64
|
+
width="20"
|
|
65
|
+
height="20"
|
|
66
|
+
viewBox="0 0 24 24"
|
|
67
|
+
fill="none"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
stroke-width="2"
|
|
70
|
+
stroke-linecap="round"
|
|
71
|
+
stroke-linejoin="round"
|
|
72
|
+
aria-hidden="true">
|
|
73
|
+
<polyline points="6 9 12 15 18 9"></polyline>
|
|
74
|
+
</svg>
|
|
75
|
+
<span class="visually-hidden">Toggle table of contents</span>
|
|
76
|
+
</button>
|
|
77
|
+
{%- endif -%}
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="enhanced-toc__content" id="toc-content">
|
|
81
|
+
{{ toc_markup }}
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div class="enhanced-toc__stats" aria-live="polite">
|
|
85
|
+
<span class="enhanced-toc__reading-progress" data-reading-progress>
|
|
86
|
+
<span class="visually-hidden">Reading progress: </span>
|
|
87
|
+
<span data-progress-percent>0</span>% complete
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
</aside>
|
|
91
|
+
|
|
92
|
+
<script>
|
|
93
|
+
(function() {
|
|
94
|
+
'use strict';
|
|
95
|
+
|
|
96
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
97
|
+
const toc = document.querySelector('[data-toc-enhanced]');
|
|
98
|
+
if (!toc) return;
|
|
99
|
+
|
|
100
|
+
const tocContent = toc.querySelector('.enhanced-toc__content');
|
|
101
|
+
const tocLinks = toc.querySelectorAll('.enhanced-toc__content a');
|
|
102
|
+
const tocToggle = toc.querySelector('[data-toc-toggle]');
|
|
103
|
+
const progressBar = toc.querySelector('[data-toc-progress]');
|
|
104
|
+
const progressPercent = toc.querySelector('[data-progress-percent]');
|
|
105
|
+
|
|
106
|
+
// Collapsible functionality
|
|
107
|
+
if (tocToggle) {
|
|
108
|
+
tocToggle.addEventListener('click', function() {
|
|
109
|
+
const isExpanded = tocToggle.getAttribute('aria-expanded') === 'true';
|
|
110
|
+
tocToggle.setAttribute('aria-expanded', !isExpanded);
|
|
111
|
+
tocContent.hidden = isExpanded;
|
|
112
|
+
toc.classList.toggle('is-collapsed', isExpanded);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get all headings that are in the TOC
|
|
117
|
+
const headings = Array.from(document.querySelectorAll('.post-content h2[id], .post-content h3[id], .post-content h4[id]'));
|
|
118
|
+
|
|
119
|
+
if (headings.length === 0) return;
|
|
120
|
+
|
|
121
|
+
// Scroll spy - highlight active section
|
|
122
|
+
function updateActiveLink() {
|
|
123
|
+
const scrollPosition = window.scrollY + 100; // Offset for better UX
|
|
124
|
+
|
|
125
|
+
let currentHeading = null;
|
|
126
|
+
for (let i = headings.length - 1; i >= 0; i--) {
|
|
127
|
+
if (headings[i].offsetTop <= scrollPosition) {
|
|
128
|
+
currentHeading = headings[i];
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
tocLinks.forEach(link => {
|
|
134
|
+
link.classList.remove('is-active');
|
|
135
|
+
if (currentHeading && link.getAttribute('href') === '#' + currentHeading.id) {
|
|
136
|
+
link.classList.add('is-active');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Reading progress
|
|
142
|
+
function updateProgress() {
|
|
143
|
+
const windowHeight = window.innerHeight;
|
|
144
|
+
const documentHeight = document.documentElement.scrollHeight;
|
|
145
|
+
const scrollTop = window.scrollY;
|
|
146
|
+
const trackLength = documentHeight - windowHeight;
|
|
147
|
+
const percentScrolled = Math.floor((scrollTop / trackLength) * 100);
|
|
148
|
+
|
|
149
|
+
if (progressBar) {
|
|
150
|
+
progressBar.style.width = percentScrolled + '%';
|
|
151
|
+
}
|
|
152
|
+
if (progressPercent) {
|
|
153
|
+
progressPercent.textContent = percentScrolled;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Smooth scroll for TOC links
|
|
158
|
+
tocLinks.forEach(link => {
|
|
159
|
+
link.addEventListener('click', function(e) {
|
|
160
|
+
const targetId = this.getAttribute('href');
|
|
161
|
+
if (targetId && targetId.startsWith('#')) {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
const targetElement = document.querySelector(targetId);
|
|
164
|
+
if (targetElement) {
|
|
165
|
+
const offsetTop = targetElement.offsetTop - 80; // Account for sticky header
|
|
166
|
+
window.scrollTo({
|
|
167
|
+
top: offsetTop,
|
|
168
|
+
behavior: 'smooth'
|
|
169
|
+
});
|
|
170
|
+
// Update URL without jumping
|
|
171
|
+
if (history.pushState) {
|
|
172
|
+
history.pushState(null, null, targetId);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Throttle scroll events for performance
|
|
180
|
+
let scrollTimeout;
|
|
181
|
+
function throttledScroll() {
|
|
182
|
+
if (scrollTimeout) {
|
|
183
|
+
window.cancelAnimationFrame(scrollTimeout);
|
|
184
|
+
}
|
|
185
|
+
scrollTimeout = window.requestAnimationFrame(function() {
|
|
186
|
+
updateActiveLink();
|
|
187
|
+
updateProgress();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
window.addEventListener('scroll', throttledScroll, { passive: true });
|
|
192
|
+
|
|
193
|
+
// Initial update
|
|
194
|
+
updateActiveLink();
|
|
195
|
+
updateProgress();
|
|
196
|
+
|
|
197
|
+
// Make TOC sticky on scroll
|
|
198
|
+
const tocTop = toc.offsetTop;
|
|
199
|
+
window.addEventListener('scroll', function() {
|
|
200
|
+
if (window.scrollY > tocTop - 20) {
|
|
201
|
+
toc.classList.add('is-sticky');
|
|
202
|
+
} else {
|
|
203
|
+
toc.classList.remove('is-sticky');
|
|
204
|
+
}
|
|
205
|
+
}, { passive: true });
|
|
206
|
+
});
|
|
207
|
+
})();
|
|
208
|
+
</script>
|
|
209
|
+
{%- endif -%}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{% assign hero_bg = page.hero_image | default: '/assets/img/20220607123041_detail.001.png' %}
|
|
2
|
+
<style nonce="{{ page.csp_nonce }}">
|
|
3
|
+
.hero { --hero-bg: url('{{ hero_bg | relative_url }}'); }
|
|
4
|
+
</style>
|
|
5
|
+
<section class="hero">
|
|
6
|
+
<div class="hero-overlay"></div>
|
|
7
|
+
<div class="container hero-content">
|
|
8
|
+
<h1>{{ page.hero_title | default: site.title }}</h1>
|
|
9
|
+
<p class="hero-tagline">{{ page.hero_tagline | default: site.hero_tagline }}</p>
|
|
10
|
+
{% if page.hero_cta_url %}
|
|
11
|
+
{% capture hero_cta_fallback %}{% t 'home.read_more' %}{% endcapture %}
|
|
12
|
+
<a class="btn btn-primary" href="{{ page.hero_cta_url | relative_url }}">{{ page.hero_cta_label | default: hero_cta_fallback | strip }}</a>
|
|
13
|
+
{% endif %}
|
|
14
|
+
</div>
|
|
15
|
+
</section>
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Multi-language Support (i18n) Component
|
|
3
|
+
Provides language switching and translated content
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- show_language_switcher: Show language dropdown (default: true)
|
|
7
|
+
- show_flags: Show country flags (default: true)
|
|
8
|
+
- position: Position (header, footer, floating) (default: header)
|
|
9
|
+
- available_languages: Languages to show (default: from config)
|
|
10
|
+
{%- endcomment -%}
|
|
11
|
+
|
|
12
|
+
{%- assign show_language_switcher = include.show_language_switcher | default: true -%}
|
|
13
|
+
{%- assign show_flags = include.show_flags | default: true -%}
|
|
14
|
+
{%- assign position = include.position | default: "header" -%}
|
|
15
|
+
|
|
16
|
+
{%- comment -%} Get available languages from config or use defaults {%- endcomment -%}
|
|
17
|
+
{%- if site.phase5_features.i18n.languages -%}
|
|
18
|
+
{%- assign available_languages = site.phase5_features.i18n.languages -%}
|
|
19
|
+
{%- else -%}
|
|
20
|
+
{%- assign available_languages = "en,es,fr,de,pt" | split: "," -%}
|
|
21
|
+
{%- endif -%}
|
|
22
|
+
|
|
23
|
+
{%- comment -%} Detect current language from page or site config {%- endcomment -%}
|
|
24
|
+
{%- assign current_lang = page.lang | default: site.lang | default: "en" -%}
|
|
25
|
+
|
|
26
|
+
{%- comment -%} Language names and flags {%- endcomment -%}
|
|
27
|
+
{%- assign lang_names = "English,Español,Français,Deutsch,Português" | split: "," -%}
|
|
28
|
+
{%- assign lang_codes = "en,es,fr,de,pt" | split: "," -%}
|
|
29
|
+
{%- assign lang_flags = "🇺🇸,🇪🇸,🇫🇷,🇩🇪,🇵🇹" | split: "," -%}
|
|
30
|
+
|
|
31
|
+
{%- if show_language_switcher -%}
|
|
32
|
+
<div class="language-switcher language-switcher--{{ position }}"
|
|
33
|
+
data-language-switcher
|
|
34
|
+
role="navigation"
|
|
35
|
+
aria-label="Language selection">
|
|
36
|
+
|
|
37
|
+
<button type="button"
|
|
38
|
+
class="language-switcher__trigger"
|
|
39
|
+
data-lang-trigger
|
|
40
|
+
aria-haspopup="true"
|
|
41
|
+
aria-expanded="false"
|
|
42
|
+
aria-controls="language-menu">
|
|
43
|
+
|
|
44
|
+
{%- comment -%} Current language display {%- endcomment -%}
|
|
45
|
+
{%- for code in lang_codes -%}
|
|
46
|
+
{%- if code == current_lang -%}
|
|
47
|
+
{%- if show_flags -%}
|
|
48
|
+
<span class="language-switcher__flag" aria-hidden="true">
|
|
49
|
+
{{ lang_flags[forloop.index0] }}
|
|
50
|
+
</span>
|
|
51
|
+
{%- endif -%}
|
|
52
|
+
<span class="language-switcher__name">
|
|
53
|
+
{{ lang_names[forloop.index0] }}
|
|
54
|
+
</span>
|
|
55
|
+
<svg class="language-switcher__icon"
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
width="16"
|
|
58
|
+
height="16"
|
|
59
|
+
viewBox="0 0 24 24"
|
|
60
|
+
fill="none"
|
|
61
|
+
stroke="currentColor"
|
|
62
|
+
stroke-width="2"
|
|
63
|
+
aria-hidden="true">
|
|
64
|
+
<polyline points="6 9 12 15 18 9"></polyline>
|
|
65
|
+
</svg>
|
|
66
|
+
{%- endif -%}
|
|
67
|
+
{%- endfor -%}
|
|
68
|
+
</button>
|
|
69
|
+
|
|
70
|
+
<ul class="language-switcher__menu"
|
|
71
|
+
id="language-menu"
|
|
72
|
+
data-lang-menu
|
|
73
|
+
role="menu"
|
|
74
|
+
hidden>
|
|
75
|
+
|
|
76
|
+
{%- for code in available_languages -%}
|
|
77
|
+
{%- assign lang_index = nil -%}
|
|
78
|
+
{%- for check_code in lang_codes -%}
|
|
79
|
+
{%- if check_code == code -%}
|
|
80
|
+
{%- assign lang_index = forloop.index0 -%}
|
|
81
|
+
{%- break -%}
|
|
82
|
+
{%- endif -%}
|
|
83
|
+
{%- endfor -%}
|
|
84
|
+
|
|
85
|
+
{%- if lang_index -%}
|
|
86
|
+
{%- assign is_current = false -%}
|
|
87
|
+
{%- if code == current_lang -%}
|
|
88
|
+
{%- assign is_current = true -%}
|
|
89
|
+
{%- endif -%}
|
|
90
|
+
|
|
91
|
+
<li class="language-switcher__item"
|
|
92
|
+
role="none">
|
|
93
|
+
<button type="button"
|
|
94
|
+
class="language-switcher__option{% if is_current %} language-switcher__option--active{% endif %}"
|
|
95
|
+
data-lang="{{ code }}"
|
|
96
|
+
role="menuitem"
|
|
97
|
+
{% if is_current %}aria-current="true"{% endif %}
|
|
98
|
+
lang="{{ code }}">
|
|
99
|
+
{%- if show_flags -%}
|
|
100
|
+
<span class="language-switcher__flag" aria-hidden="true">
|
|
101
|
+
{{ lang_flags[lang_index] }}
|
|
102
|
+
</span>
|
|
103
|
+
{%- endif -%}
|
|
104
|
+
<span class="language-switcher__label">
|
|
105
|
+
{{ lang_names[lang_index] }}
|
|
106
|
+
</span>
|
|
107
|
+
{%- if is_current -%}
|
|
108
|
+
<svg class="language-switcher__check"
|
|
109
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
110
|
+
width="16"
|
|
111
|
+
height="16"
|
|
112
|
+
viewBox="0 0 24 24"
|
|
113
|
+
fill="none"
|
|
114
|
+
stroke="currentColor"
|
|
115
|
+
stroke-width="2"
|
|
116
|
+
aria-hidden="true">
|
|
117
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
118
|
+
</svg>
|
|
119
|
+
{%- endif -%}
|
|
120
|
+
</button>
|
|
121
|
+
</li>
|
|
122
|
+
{%- endif -%}
|
|
123
|
+
{%- endfor -%}
|
|
124
|
+
</ul>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{%- comment -%} Translation strings for common UI elements {%- endcomment -%}
|
|
128
|
+
<script type="application/json" data-i18n-strings>
|
|
129
|
+
{
|
|
130
|
+
"en": {
|
|
131
|
+
"readMore": "Read more",
|
|
132
|
+
"readLess": "Read less",
|
|
133
|
+
"share": "Share",
|
|
134
|
+
"bookmark": "Bookmark",
|
|
135
|
+
"unbookmark": "Remove bookmark",
|
|
136
|
+
"comments": "Comments",
|
|
137
|
+
"noComments": "No comments yet",
|
|
138
|
+
"loading": "Loading...",
|
|
139
|
+
"error": "An error occurred",
|
|
140
|
+
"publishedOn": "Published on",
|
|
141
|
+
"updatedOn": "Updated on",
|
|
142
|
+
"by": "by",
|
|
143
|
+
"in": "in",
|
|
144
|
+
"minutes": "minutes",
|
|
145
|
+
"minRead": "min read",
|
|
146
|
+
"relatedPosts": "Related Posts",
|
|
147
|
+
"popularPosts": "Popular Posts",
|
|
148
|
+
"recentPosts": "Recent Posts",
|
|
149
|
+
"categories": "Categories",
|
|
150
|
+
"tags": "Tags",
|
|
151
|
+
"searchPlaceholder": "Search posts...",
|
|
152
|
+
"noResults": "No results found",
|
|
153
|
+
"showingResults": "Showing {{count}} results"
|
|
154
|
+
},
|
|
155
|
+
"es": {
|
|
156
|
+
"readMore": "Leer más",
|
|
157
|
+
"readLess": "Leer menos",
|
|
158
|
+
"share": "Compartir",
|
|
159
|
+
"bookmark": "Guardar",
|
|
160
|
+
"unbookmark": "Eliminar marcador",
|
|
161
|
+
"comments": "Comentarios",
|
|
162
|
+
"noComments": "Aún no hay comentarios",
|
|
163
|
+
"loading": "Cargando...",
|
|
164
|
+
"error": "Ocurrió un error",
|
|
165
|
+
"publishedOn": "Publicado el",
|
|
166
|
+
"updatedOn": "Actualizado el",
|
|
167
|
+
"by": "por",
|
|
168
|
+
"in": "en",
|
|
169
|
+
"minutes": "minutos",
|
|
170
|
+
"minRead": "min de lectura",
|
|
171
|
+
"relatedPosts": "Artículos Relacionados",
|
|
172
|
+
"popularPosts": "Artículos Populares",
|
|
173
|
+
"recentPosts": "Artículos Recientes",
|
|
174
|
+
"categories": "Categorías",
|
|
175
|
+
"tags": "Etiquetas",
|
|
176
|
+
"searchPlaceholder": "Buscar artículos...",
|
|
177
|
+
"noResults": "No se encontraron resultados",
|
|
178
|
+
"showingResults": "Mostrando {{count}} resultados"
|
|
179
|
+
},
|
|
180
|
+
"fr": {
|
|
181
|
+
"readMore": "Lire la suite",
|
|
182
|
+
"readLess": "Lire moins",
|
|
183
|
+
"share": "Partager",
|
|
184
|
+
"bookmark": "Enregistrer",
|
|
185
|
+
"unbookmark": "Supprimer le signet",
|
|
186
|
+
"comments": "Commentaires",
|
|
187
|
+
"noComments": "Pas encore de commentaires",
|
|
188
|
+
"loading": "Chargement...",
|
|
189
|
+
"error": "Une erreur s'est produite",
|
|
190
|
+
"publishedOn": "Publié le",
|
|
191
|
+
"updatedOn": "Mis à jour le",
|
|
192
|
+
"by": "par",
|
|
193
|
+
"in": "dans",
|
|
194
|
+
"minutes": "minutes",
|
|
195
|
+
"minRead": "min de lecture",
|
|
196
|
+
"relatedPosts": "Articles Connexes",
|
|
197
|
+
"popularPosts": "Articles Populaires",
|
|
198
|
+
"recentPosts": "Articles Récents",
|
|
199
|
+
"categories": "Catégories",
|
|
200
|
+
"tags": "Étiquettes",
|
|
201
|
+
"searchPlaceholder": "Rechercher des articles...",
|
|
202
|
+
"noResults": "Aucun résultat trouvé",
|
|
203
|
+
"showingResults": "Affichage de {{count}} résultats"
|
|
204
|
+
},
|
|
205
|
+
"de": {
|
|
206
|
+
"readMore": "Weiterlesen",
|
|
207
|
+
"readLess": "Weniger lesen",
|
|
208
|
+
"share": "Teilen",
|
|
209
|
+
"bookmark": "Lesezeichen",
|
|
210
|
+
"unbookmark": "Lesezeichen entfernen",
|
|
211
|
+
"comments": "Kommentare",
|
|
212
|
+
"noComments": "Noch keine Kommentare",
|
|
213
|
+
"loading": "Wird geladen...",
|
|
214
|
+
"error": "Ein Fehler ist aufgetreten",
|
|
215
|
+
"publishedOn": "Veröffentlicht am",
|
|
216
|
+
"updatedOn": "Aktualisiert am",
|
|
217
|
+
"by": "von",
|
|
218
|
+
"in": "in",
|
|
219
|
+
"minutes": "Minuten",
|
|
220
|
+
"minRead": "Min. Lesezeit",
|
|
221
|
+
"relatedPosts": "Verwandte Beiträge",
|
|
222
|
+
"popularPosts": "Beliebte Beiträge",
|
|
223
|
+
"recentPosts": "Neueste Beiträge",
|
|
224
|
+
"categories": "Kategorien",
|
|
225
|
+
"tags": "Schlagwörter",
|
|
226
|
+
"searchPlaceholder": "Beiträge durchsuchen...",
|
|
227
|
+
"noResults": "Keine Ergebnisse gefunden",
|
|
228
|
+
"showingResults": "Zeige {{count}} Ergebnisse"
|
|
229
|
+
},
|
|
230
|
+
"pt": {
|
|
231
|
+
"readMore": "Ler mais",
|
|
232
|
+
"readLess": "Ler menos",
|
|
233
|
+
"share": "Compartilhar",
|
|
234
|
+
"bookmark": "Salvar",
|
|
235
|
+
"unbookmark": "Remover marcador",
|
|
236
|
+
"comments": "Comentários",
|
|
237
|
+
"noComments": "Ainda sem comentários",
|
|
238
|
+
"loading": "Carregando...",
|
|
239
|
+
"error": "Ocorreu um erro",
|
|
240
|
+
"publishedOn": "Publicado em",
|
|
241
|
+
"updatedOn": "Atualizado em",
|
|
242
|
+
"by": "por",
|
|
243
|
+
"in": "em",
|
|
244
|
+
"minutes": "minutos",
|
|
245
|
+
"minRead": "min de leitura",
|
|
246
|
+
"relatedPosts": "Artigos Relacionados",
|
|
247
|
+
"popularPosts": "Artigos Populares",
|
|
248
|
+
"recentPosts": "Artigos Recentes",
|
|
249
|
+
"categories": "Categorias",
|
|
250
|
+
"tags": "Tags",
|
|
251
|
+
"searchPlaceholder": "Pesquisar artigos...",
|
|
252
|
+
"noResults": "Nenhum resultado encontrado",
|
|
253
|
+
"showingResults": "Mostrando {{count}} resultados"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
<script>
|
|
259
|
+
(function() {
|
|
260
|
+
'use strict';
|
|
261
|
+
|
|
262
|
+
const STORAGE_KEY = 'preferredLanguage';
|
|
263
|
+
|
|
264
|
+
class LanguageSwitcher {
|
|
265
|
+
constructor() {
|
|
266
|
+
this.currentLang = '{{ current_lang }}';
|
|
267
|
+
this.translations = {};
|
|
268
|
+
this.init();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
init() {
|
|
272
|
+
// Load translation strings
|
|
273
|
+
const stringsEl = document.querySelector('[data-i18n-strings]');
|
|
274
|
+
if (stringsEl) {
|
|
275
|
+
try {
|
|
276
|
+
this.translations = JSON.parse(stringsEl.textContent);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
console.error('Failed to parse translation strings:', e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Restore preferred language
|
|
283
|
+
const savedLang = this.getPreferredLanguage();
|
|
284
|
+
if (savedLang && savedLang !== this.currentLang) {
|
|
285
|
+
this.switchLanguage(savedLang);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.attachEventListeners();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
attachEventListeners() {
|
|
292
|
+
const trigger = document.querySelector('[data-lang-trigger]');
|
|
293
|
+
const menu = document.querySelector('[data-lang-menu]');
|
|
294
|
+
const options = document.querySelectorAll('[data-lang]');
|
|
295
|
+
|
|
296
|
+
if (!trigger || !menu) return;
|
|
297
|
+
|
|
298
|
+
// Toggle menu
|
|
299
|
+
trigger.addEventListener('click', () => {
|
|
300
|
+
const isHidden = menu.hidden;
|
|
301
|
+
menu.hidden = !isHidden;
|
|
302
|
+
trigger.setAttribute('aria-expanded', isHidden);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Close on outside click
|
|
306
|
+
document.addEventListener('click', (e) => {
|
|
307
|
+
if (!e.target.closest('[data-language-switcher]')) {
|
|
308
|
+
menu.hidden = true;
|
|
309
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Language selection
|
|
314
|
+
options.forEach(option => {
|
|
315
|
+
option.addEventListener('click', () => {
|
|
316
|
+
const lang = option.dataset.lang;
|
|
317
|
+
this.switchLanguage(lang);
|
|
318
|
+
menu.hidden = true;
|
|
319
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Keyboard navigation
|
|
324
|
+
menu.addEventListener('keydown', (e) => {
|
|
325
|
+
if (e.key === 'Escape') {
|
|
326
|
+
menu.hidden = true;
|
|
327
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
328
|
+
trigger.focus();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
switchLanguage(lang) {
|
|
334
|
+
this.currentLang = lang;
|
|
335
|
+
this.savePreferredLanguage(lang);
|
|
336
|
+
|
|
337
|
+
// Update document language
|
|
338
|
+
document.documentElement.lang = lang;
|
|
339
|
+
|
|
340
|
+
// Reload page with language parameter
|
|
341
|
+
const url = new URL(window.location.href);
|
|
342
|
+
url.searchParams.set('lang', lang);
|
|
343
|
+
|
|
344
|
+
// Store scroll position
|
|
345
|
+
sessionStorage.setItem('scrollPosition', window.pageYOffset);
|
|
346
|
+
|
|
347
|
+
window.location.href = url.toString();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getPreferredLanguage() {
|
|
351
|
+
try {
|
|
352
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
353
|
+
} catch (e) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
savePreferredLanguage(lang) {
|
|
359
|
+
try {
|
|
360
|
+
localStorage.setItem(STORAGE_KEY, lang);
|
|
361
|
+
} catch (e) {
|
|
362
|
+
console.error('Failed to save language preference:', e);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Translate a key
|
|
367
|
+
t(key, replacements = {}) {
|
|
368
|
+
let text = this.translations[this.currentLang]?.[key] || key;
|
|
369
|
+
|
|
370
|
+
// Replace placeholders
|
|
371
|
+
Object.keys(replacements).forEach(placeholder => {
|
|
372
|
+
text = text.replace(`{{${placeholder}}}`, replacements[placeholder]);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return text;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Initialize
|
|
380
|
+
const languageSwitcher = new LanguageSwitcher();
|
|
381
|
+
|
|
382
|
+
// Make available globally
|
|
383
|
+
window.i18n = {
|
|
384
|
+
t: (key, replacements) => languageSwitcher.t(key, replacements),
|
|
385
|
+
currentLang: () => languageSwitcher.currentLang
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Restore scroll position
|
|
389
|
+
const scrollPos = sessionStorage.getItem('scrollPosition');
|
|
390
|
+
if (scrollPos) {
|
|
391
|
+
window.scrollTo(0, parseInt(scrollPos, 10));
|
|
392
|
+
sessionStorage.removeItem('scrollPosition');
|
|
393
|
+
}
|
|
394
|
+
})();
|
|
395
|
+
</script>
|
|
396
|
+
{%- endif -%}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
optional:
|
|
4
|
+
- expressions
|
|
5
|
+
---
|
|
6
|
+
Renders a noscript fallback for MathJax expressions when available.
|
|
7
|
+
{%- endcomment -%}
|
|
8
|
+
{%- assign expressions = include.expressions | default: '' -%}
|
|
9
|
+
{%- if expressions != blank and expressions != empty and expressions.size > 0 -%}
|
|
10
|
+
<noscript class="math-noscript">
|
|
11
|
+
<p class="math-noscript__message">Enable JavaScript to render equations.</p>
|
|
12
|
+
{%- for expression in expressions -%}
|
|
13
|
+
<pre class="math-noscript__code">{{ expression.latex | escape }}</pre>
|
|
14
|
+
{%- if expression.alt -%}
|
|
15
|
+
<p class="math-noscript__alt">{{ expression.alt }}</p>
|
|
16
|
+
{%- endif -%}
|
|
17
|
+
{%- endfor -%}
|
|
18
|
+
</noscript>
|
|
19
|
+
{%- endif -%}
|