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,244 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Comment System Integration Component
|
|
3
|
+
Supports multiple comment platforms: Giscus, Utterances, Disqus, Commento
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- provider: Comment provider (giscus, utterances, disqus, commento, custom) (default: from config)
|
|
7
|
+
- enable_comments: Override to enable/disable comments (default: true)
|
|
8
|
+
- lazy_load: Lazy load comments on scroll (default: true)
|
|
9
|
+
- show_count: Show comment count (default: true)
|
|
10
|
+
{%- endcomment -%}
|
|
11
|
+
|
|
12
|
+
{%- assign provider = include.provider | default: site.phase3_features.comments.provider | default: "giscus" -%}
|
|
13
|
+
{%- assign enable_comments = include.enable_comments | default: page.comments | default: true -%}
|
|
14
|
+
{%- assign lazy_load = include.lazy_load | default: site.phase3_features.comments.lazy_load | default: true -%}
|
|
15
|
+
{%- assign show_count = include.show_count | default: true -%}
|
|
16
|
+
|
|
17
|
+
{%- comment -%} Get configuration from site config {%- endcomment -%}
|
|
18
|
+
{%- assign comments_config = site.phase3_features.comments -%}
|
|
19
|
+
|
|
20
|
+
{%- if enable_comments and provider != "none" -%}
|
|
21
|
+
<section class="comments-section"
|
|
22
|
+
data-comments
|
|
23
|
+
data-provider="{{ provider }}"
|
|
24
|
+
id="comments"
|
|
25
|
+
aria-label="Comments section">
|
|
26
|
+
|
|
27
|
+
<div class="comments-section__header">
|
|
28
|
+
<h3 class="comments-section__title">
|
|
29
|
+
<svg class="comments-section__icon"
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
width="24"
|
|
32
|
+
height="24"
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
stroke-width="2"
|
|
37
|
+
stroke-linecap="round"
|
|
38
|
+
stroke-linejoin="round"
|
|
39
|
+
aria-hidden="true">
|
|
40
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
41
|
+
</svg>
|
|
42
|
+
Comments
|
|
43
|
+
</h3>
|
|
44
|
+
|
|
45
|
+
{%- if show_count -%}
|
|
46
|
+
<span class="comments-section__count"
|
|
47
|
+
data-comment-count
|
|
48
|
+
aria-live="polite">
|
|
49
|
+
Loading...
|
|
50
|
+
</span>
|
|
51
|
+
{%- endif -%}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
{%- if lazy_load -%}
|
|
55
|
+
<div class="comments-section__placeholder" data-comments-placeholder>
|
|
56
|
+
<button type="button"
|
|
57
|
+
class="comments-section__load-button"
|
|
58
|
+
data-load-comments
|
|
59
|
+
aria-label="Load comments">
|
|
60
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
61
|
+
width="20"
|
|
62
|
+
height="20"
|
|
63
|
+
viewBox="0 0 24 24"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
stroke-width="2"
|
|
67
|
+
stroke-linecap="round"
|
|
68
|
+
stroke-linejoin="round"
|
|
69
|
+
aria-hidden="true">
|
|
70
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
71
|
+
</svg>
|
|
72
|
+
Click to load comments
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
{%- endif -%}
|
|
76
|
+
|
|
77
|
+
<div class="comments-section__container"
|
|
78
|
+
data-comments-container
|
|
79
|
+
{% if lazy_load %}style="display: none;"{% endif %}>
|
|
80
|
+
|
|
81
|
+
{%- case provider -%}
|
|
82
|
+
|
|
83
|
+
{%- when "giscus" -%}
|
|
84
|
+
{%- comment -%} Giscus (GitHub Discussions) {%- endcomment -%}
|
|
85
|
+
<script src="https://giscus.app/client.js"
|
|
86
|
+
data-repo="{{ comments_config.giscus.repo }}"
|
|
87
|
+
data-repo-id="{{ comments_config.giscus.repo_id }}"
|
|
88
|
+
data-category="{{ comments_config.giscus.category }}"
|
|
89
|
+
data-category-id="{{ comments_config.giscus.category_id }}"
|
|
90
|
+
data-mapping="{{ comments_config.giscus.mapping | default: 'pathname' }}"
|
|
91
|
+
data-strict="{{ comments_config.giscus.strict | default: '0' }}"
|
|
92
|
+
data-reactions-enabled="{{ comments_config.giscus.reactions_enabled | default: '1' }}"
|
|
93
|
+
data-emit-metadata="{{ comments_config.giscus.emit_metadata | default: '0' }}"
|
|
94
|
+
data-input-position="{{ comments_config.giscus.input_position | default: 'bottom' }}"
|
|
95
|
+
data-theme="{{ comments_config.giscus.theme | default: 'preferred_color_scheme' }}"
|
|
96
|
+
data-lang="{{ comments_config.giscus.lang | default: 'en' }}"
|
|
97
|
+
data-loading="{{ comments_config.giscus.loading | default: 'lazy' }}"
|
|
98
|
+
crossorigin="anonymous"
|
|
99
|
+
async>
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
{%- when "utterances" -%}
|
|
103
|
+
{%- comment -%} Utterances (GitHub Issues) {%- endcomment -%}
|
|
104
|
+
<script src="https://utteranc.es/client.js"
|
|
105
|
+
repo="{{ comments_config.utterances.repo }}"
|
|
106
|
+
issue-term="{{ comments_config.utterances.issue_term | default: 'pathname' }}"
|
|
107
|
+
label="{{ comments_config.utterances.label | default: 'comment' }}"
|
|
108
|
+
theme="{{ comments_config.utterances.theme | default: 'github-light' }}"
|
|
109
|
+
crossorigin="anonymous"
|
|
110
|
+
async>
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
{%- when "disqus" -%}
|
|
114
|
+
{%- comment -%} Disqus {%- endcomment -%}
|
|
115
|
+
<div id="disqus_thread"></div>
|
|
116
|
+
<script>
|
|
117
|
+
var disqus_config = function () {
|
|
118
|
+
this.page.url = "{{ page.url | absolute_url }}";
|
|
119
|
+
this.page.identifier = "{{ page.id }}";
|
|
120
|
+
this.page.title = "{{ page.title }}";
|
|
121
|
+
};
|
|
122
|
+
(function() {
|
|
123
|
+
var d = document, s = d.createElement('script');
|
|
124
|
+
s.src = 'https://{{ comments_config.disqus.shortname }}.disqus.com/embed.js';
|
|
125
|
+
s.setAttribute('data-timestamp', +new Date());
|
|
126
|
+
(d.head || d.body).appendChild(s);
|
|
127
|
+
})();
|
|
128
|
+
</script>
|
|
129
|
+
<noscript>
|
|
130
|
+
Please enable JavaScript to view the
|
|
131
|
+
<a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a>
|
|
132
|
+
</noscript>
|
|
133
|
+
|
|
134
|
+
{%- when "commento" -%}
|
|
135
|
+
{%- comment -%} Commento {%- endcomment -%}
|
|
136
|
+
<div id="commento"></div>
|
|
137
|
+
<script defer
|
|
138
|
+
src="{{ comments_config.commento.url | default: 'https://cdn.commento.io' }}/js/commento.js"
|
|
139
|
+
{% if comments_config.commento.no_fonts %}data-no-fonts="true"{% endif %}
|
|
140
|
+
{% if comments_config.commento.hide_deleted %}data-hide-deleted="true"{% endif %}
|
|
141
|
+
{% if comments_config.commento.auto_init == false %}data-auto-init="false"{% endif %}>
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
{%- when "custom" -%}
|
|
145
|
+
{%- comment -%} Custom HTML for other providers {%- endcomment -%}
|
|
146
|
+
<div class="comments-section__custom">
|
|
147
|
+
{{ comments_config.custom_html }}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{%- endcase -%}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{%- comment -%} Privacy notice {%- endcomment -%}
|
|
154
|
+
{%- if comments_config.show_privacy_notice -%}
|
|
155
|
+
<div class="comments-section__privacy">
|
|
156
|
+
<small>
|
|
157
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
158
|
+
width="14"
|
|
159
|
+
height="14"
|
|
160
|
+
viewBox="0 0 24 24"
|
|
161
|
+
fill="none"
|
|
162
|
+
stroke="currentColor"
|
|
163
|
+
stroke-width="2"
|
|
164
|
+
stroke-linecap="round"
|
|
165
|
+
stroke-linejoin="round"
|
|
166
|
+
aria-hidden="true">
|
|
167
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
168
|
+
<line x1="12" y1="16" x2="12" y2="12"></line>
|
|
169
|
+
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
|
170
|
+
</svg>
|
|
171
|
+
Comments are powered by {{ provider | capitalize }}.
|
|
172
|
+
{%- if comments_config.privacy_url -%}
|
|
173
|
+
See our <a href="{{ comments_config.privacy_url }}">privacy policy</a>.
|
|
174
|
+
{%- endif -%}
|
|
175
|
+
</small>
|
|
176
|
+
</div>
|
|
177
|
+
{%- endif -%}
|
|
178
|
+
</section>
|
|
179
|
+
|
|
180
|
+
{%- if lazy_load -%}
|
|
181
|
+
<script>
|
|
182
|
+
(function() {
|
|
183
|
+
'use strict';
|
|
184
|
+
|
|
185
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
186
|
+
const commentsSection = document.querySelector('[data-comments]');
|
|
187
|
+
if (!commentsSection) return;
|
|
188
|
+
|
|
189
|
+
const loadButton = commentsSection.querySelector('[data-load-comments]');
|
|
190
|
+
const placeholder = commentsSection.querySelector('[data-comments-placeholder]');
|
|
191
|
+
const container = commentsSection.querySelector('[data-comments-container]');
|
|
192
|
+
|
|
193
|
+
function loadComments() {
|
|
194
|
+
if (placeholder) placeholder.style.display = 'none';
|
|
195
|
+
if (container) container.style.display = 'block';
|
|
196
|
+
|
|
197
|
+
// Execute any scripts in the container
|
|
198
|
+
const scripts = container.querySelectorAll('script');
|
|
199
|
+
scripts.forEach(script => {
|
|
200
|
+
const newScript = document.createElement('script');
|
|
201
|
+
Array.from(script.attributes).forEach(attr => {
|
|
202
|
+
newScript.setAttribute(attr.name, attr.value);
|
|
203
|
+
});
|
|
204
|
+
newScript.textContent = script.textContent;
|
|
205
|
+
script.parentNode.replaceChild(newScript, script);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Load on button click
|
|
210
|
+
if (loadButton) {
|
|
211
|
+
loadButton.addEventListener('click', loadComments);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Optionally load when scrolling near comments
|
|
215
|
+
const observer = new IntersectionObserver((entries) => {
|
|
216
|
+
entries.forEach(entry => {
|
|
217
|
+
if (entry.isIntersecting && entry.intersectionRatio > 0.1) {
|
|
218
|
+
// Auto-load when scrolling near (optional)
|
|
219
|
+
// loadComments();
|
|
220
|
+
// observer.disconnect();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}, {
|
|
224
|
+
rootMargin: '200px',
|
|
225
|
+
threshold: 0.1
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
observer.observe(commentsSection);
|
|
229
|
+
});
|
|
230
|
+
})();
|
|
231
|
+
</script>
|
|
232
|
+
{%- endif -%}
|
|
233
|
+
|
|
234
|
+
{%- comment -%} Comment count script (for supported providers) {%- endcomment -%}
|
|
235
|
+
{%- if show_count -%}
|
|
236
|
+
{%- if provider == "disqus" -%}
|
|
237
|
+
<script id="dsq-count-scr"
|
|
238
|
+
src="//{{ comments_config.disqus.shortname }}.disqus.com/count.js"
|
|
239
|
+
async>
|
|
240
|
+
</script>
|
|
241
|
+
{%- endif -%}
|
|
242
|
+
{%- endif -%}
|
|
243
|
+
|
|
244
|
+
{%- endif -%}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
required:
|
|
4
|
+
- page
|
|
5
|
+
optional:
|
|
6
|
+
- date_format
|
|
7
|
+
---
|
|
8
|
+
Editorial provenance note built from the `why_this_exists`, `evidence`,
|
|
9
|
+
`methodology` and `reviewed_at` front-matter fields. Renders nothing when
|
|
10
|
+
none of them is set.
|
|
11
|
+
{%- endcomment -%}
|
|
12
|
+
{%- assign prov = include.page -%}
|
|
13
|
+
{%- if prov.why_this_exists or prov.evidence or prov.methodology or prov.reviewed_at -%}
|
|
14
|
+
<aside class="content-provenance" aria-labelledby="content-provenance-heading">
|
|
15
|
+
<h2 id="content-provenance-heading" class="content-provenance__heading">{% t 'post.provenance.heading' %}</h2>
|
|
16
|
+
<dl class="content-provenance__list">
|
|
17
|
+
{%- if prov.why_this_exists -%}
|
|
18
|
+
<div>
|
|
19
|
+
<dt>{% t 'post.provenance.why' %}</dt>
|
|
20
|
+
<dd>{{ prov.why_this_exists | strip_newlines }}</dd>
|
|
21
|
+
</div>
|
|
22
|
+
{%- endif -%}
|
|
23
|
+
{%- if prov.evidence -%}
|
|
24
|
+
<div>
|
|
25
|
+
<dt>{% t 'post.provenance.evidence' %}</dt>
|
|
26
|
+
<dd>{{ prov.evidence | strip_newlines }}</dd>
|
|
27
|
+
</div>
|
|
28
|
+
{%- endif -%}
|
|
29
|
+
{%- if prov.methodology -%}
|
|
30
|
+
<div>
|
|
31
|
+
<dt>{% t 'post.provenance.method' %}</dt>
|
|
32
|
+
<dd>{{ prov.methodology | strip_newlines }}</dd>
|
|
33
|
+
</div>
|
|
34
|
+
{%- endif -%}
|
|
35
|
+
{%- if prov.reviewed_at -%}
|
|
36
|
+
<div>
|
|
37
|
+
<dt>{% t 'post.provenance.reviewed' %}</dt>
|
|
38
|
+
<dd>{% include helpers/date-format.html date=prov.reviewed_at format=include.date_format %}</dd>
|
|
39
|
+
</div>
|
|
40
|
+
{%- endif -%}
|
|
41
|
+
</dl>
|
|
42
|
+
</aside>
|
|
43
|
+
{%- endif -%}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Content Recommendations Component
|
|
3
|
+
Displays related/recommended posts based on similarity scoring
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- current_post: Current post object (default: page)
|
|
7
|
+
- max_recommendations: Maximum posts to show (default: 3)
|
|
8
|
+
- algorithm: Scoring algorithm (tag-based, category-based, hybrid) (default: hybrid)
|
|
9
|
+
- show_score: Show similarity score (default: false, for debugging)
|
|
10
|
+
- layout: Display layout (grid, list, compact) (default: grid)
|
|
11
|
+
- heading: Custom heading (default: "Recommended for You")
|
|
12
|
+
{%- endcomment -%}
|
|
13
|
+
|
|
14
|
+
{%- assign current_post = include.current_post | default: page -%}
|
|
15
|
+
{%- assign max_recommendations = include.max_recommendations | default: 3 -%}
|
|
16
|
+
{%- assign algorithm = include.algorithm | default: "hybrid" -%}
|
|
17
|
+
{%- assign show_score = include.show_score | default: false -%}
|
|
18
|
+
{%- assign layout = include.layout | default: "grid" -%}
|
|
19
|
+
{%- assign heading = include.heading | default: "Recommended for You" -%}
|
|
20
|
+
|
|
21
|
+
{%- comment -%} Get current post metadata {%- endcomment -%}
|
|
22
|
+
{%- assign current_tags = current_post.tags | default: empty -%}
|
|
23
|
+
{%- assign current_categories = current_post.categories | default: empty -%}
|
|
24
|
+
{%- assign current_difficulty = current_post.difficulty | default: "" -%}
|
|
25
|
+
{%- assign current_series = current_post.series | default: "" -%}
|
|
26
|
+
|
|
27
|
+
{%- comment -%} Initialize recommendations array {%- endcomment -%}
|
|
28
|
+
{%- assign recommendations = "" | split: "" -%}
|
|
29
|
+
{%- assign scored_posts = "" | split: "" -%}
|
|
30
|
+
|
|
31
|
+
{%- comment -%} Score all posts except current {%- endcomment -%}
|
|
32
|
+
{%- for post in site.posts -%}
|
|
33
|
+
{%- if post.url != current_post.url -%}
|
|
34
|
+
{%- assign score = 0 -%}
|
|
35
|
+
|
|
36
|
+
{%- comment -%} Series bonus (highest priority) {%- endcomment -%}
|
|
37
|
+
{%- if current_series != "" and post.series == current_series -%}
|
|
38
|
+
{%- assign score = score | plus: 100 -%}
|
|
39
|
+
{%- endif -%}
|
|
40
|
+
|
|
41
|
+
{%- comment -%} Tag matching {%- endcomment -%}
|
|
42
|
+
{%- if algorithm == "tag-based" or algorithm == "hybrid" -%}
|
|
43
|
+
{%- for tag in current_tags -%}
|
|
44
|
+
{%- if post.tags contains tag -%}
|
|
45
|
+
{%- assign score = score | plus: 10 -%}
|
|
46
|
+
{%- endif -%}
|
|
47
|
+
{%- endfor -%}
|
|
48
|
+
{%- endif -%}
|
|
49
|
+
|
|
50
|
+
{%- comment -%} Category matching {%- endcomment -%}
|
|
51
|
+
{%- if algorithm == "category-based" or algorithm == "hybrid" -%}
|
|
52
|
+
{%- for category in current_categories -%}
|
|
53
|
+
{%- if post.categories contains category -%}
|
|
54
|
+
{%- assign score = score | plus: 15 -%}
|
|
55
|
+
{%- endif -%}
|
|
56
|
+
{%- endfor -%}
|
|
57
|
+
{%- endif -%}
|
|
58
|
+
|
|
59
|
+
{%- comment -%} Difficulty matching {%- endcomment -%}
|
|
60
|
+
{%- if current_difficulty != "" and post.difficulty == current_difficulty -%}
|
|
61
|
+
{%- assign score = score | plus: 5 -%}
|
|
62
|
+
{%- endif -%}
|
|
63
|
+
|
|
64
|
+
{%- comment -%} Recency bonus (prefer recent posts) {%- endcomment -%}
|
|
65
|
+
{%- assign days_old = "now" | date: "%s" | minus: post.date | date: "%s" | divided_by: 86400 -%}
|
|
66
|
+
{%- if days_old < 30 -%}
|
|
67
|
+
{%- assign score = score | plus: 3 -%}
|
|
68
|
+
{%- elsif days_old < 90 -%}
|
|
69
|
+
{%- assign score = score | plus: 1 -%}
|
|
70
|
+
{%- endif -%}
|
|
71
|
+
|
|
72
|
+
{%- comment -%} Store post with score {%- endcomment -%}
|
|
73
|
+
{%- if score > 0 -%}
|
|
74
|
+
{%- assign post_with_score = post.url | append: "||" | append: score -%}
|
|
75
|
+
{%- assign scored_posts = scored_posts | push: post_with_score -%}
|
|
76
|
+
{%- endif -%}
|
|
77
|
+
{%- endif -%}
|
|
78
|
+
{%- endfor -%}
|
|
79
|
+
|
|
80
|
+
{%- comment -%} Sort by score (manual bubble sort since Liquid doesn't have sort by custom field) {%- endcomment -%}
|
|
81
|
+
{%- assign sorted_posts = scored_posts -%}
|
|
82
|
+
{%- for i in (1..scored_posts.size) -%}
|
|
83
|
+
{%- for j in (1..scored_posts.size) -%}
|
|
84
|
+
{%- assign current_item = sorted_posts[j] | split: "||" -%}
|
|
85
|
+
{%- assign prev_item = sorted_posts[j-1] | split: "||" -%}
|
|
86
|
+
{%- if current_item[1] and prev_item[1] -%}
|
|
87
|
+
{%- assign current_score = current_item[1] | plus: 0 -%}
|
|
88
|
+
{%- assign prev_score = prev_item[1] | plus: 0 -%}
|
|
89
|
+
{%- if current_score > prev_score -%}
|
|
90
|
+
{%- comment -%} Swap items {%- endcomment -%}
|
|
91
|
+
{%- assign temp = sorted_posts[j] -%}
|
|
92
|
+
{%- assign sorted_posts[j] = sorted_posts[j-1] -%}
|
|
93
|
+
{%- assign sorted_posts[j-1] = temp -%}
|
|
94
|
+
{%- endif -%}
|
|
95
|
+
{%- endif -%}
|
|
96
|
+
{%- endfor -%}
|
|
97
|
+
{%- endfor -%}
|
|
98
|
+
|
|
99
|
+
{%- comment -%} Get top N recommendations {%- endcomment -%}
|
|
100
|
+
{%- assign recommendation_count = 0 -%}
|
|
101
|
+
{%- for item in sorted_posts limit: max_recommendations -%}
|
|
102
|
+
{%- assign item_parts = item | split: "||" -%}
|
|
103
|
+
{%- assign post_url = item_parts[0] -%}
|
|
104
|
+
{%- assign post_score = item_parts[1] -%}
|
|
105
|
+
|
|
106
|
+
{%- comment -%} Find the actual post object {%- endcomment -%}
|
|
107
|
+
{%- for post in site.posts -%}
|
|
108
|
+
{%- if post.url == post_url -%}
|
|
109
|
+
{%- assign recommendations = recommendations | push: post -%}
|
|
110
|
+
{%- assign recommendation_count = recommendation_count | plus: 1 -%}
|
|
111
|
+
{%- break -%}
|
|
112
|
+
{%- endif -%}
|
|
113
|
+
{%- endfor -%}
|
|
114
|
+
{%- endfor -%}
|
|
115
|
+
|
|
116
|
+
{%- comment -%} Display recommendations if we have any {%- endcomment -%}
|
|
117
|
+
{%- if recommendation_count > 0 -%}
|
|
118
|
+
<aside class="content-recommendations content-recommendations--{{ layout }}"
|
|
119
|
+
data-recommendations
|
|
120
|
+
aria-label="Recommended articles">
|
|
121
|
+
|
|
122
|
+
<h3 class="content-recommendations__heading">{{ heading }}</h3>
|
|
123
|
+
|
|
124
|
+
<div class="content-recommendations__list">
|
|
125
|
+
{%- for post in recommendations limit: max_recommendations -%}
|
|
126
|
+
{%- comment -%} Get recommendation score for display {%- endcomment -%}
|
|
127
|
+
{%- assign post_score = 0 -%}
|
|
128
|
+
{%- if show_score -%}
|
|
129
|
+
{%- for item in sorted_posts -%}
|
|
130
|
+
{%- assign item_parts = item | split: "||" -%}
|
|
131
|
+
{%- if item_parts[0] == post.url -%}
|
|
132
|
+
{%- assign post_score = item_parts[1] -%}
|
|
133
|
+
{%- break -%}
|
|
134
|
+
{%- endif -%}
|
|
135
|
+
{%- endfor -%}
|
|
136
|
+
{%- endif -%}
|
|
137
|
+
|
|
138
|
+
<article class="recommendation-card"
|
|
139
|
+
itemscope
|
|
140
|
+
itemtype="https://schema.org/BlogPosting">
|
|
141
|
+
|
|
142
|
+
{%- if post.featured_image or post.image -%}
|
|
143
|
+
<div class="recommendation-card__image">
|
|
144
|
+
<a href="{{ post.url | relative_url }}" tabindex="-1" aria-hidden="true">
|
|
145
|
+
<img src="{{ post.featured_image | default: post.image | relative_url }}"
|
|
146
|
+
alt="{{ post.title }}"
|
|
147
|
+
loading="lazy"
|
|
148
|
+
itemprop="image">
|
|
149
|
+
</a>
|
|
150
|
+
</div>
|
|
151
|
+
{%- endif -%}
|
|
152
|
+
|
|
153
|
+
<div class="recommendation-card__content">
|
|
154
|
+
<h4 class="recommendation-card__title" itemprop="headline">
|
|
155
|
+
<a href="{{ post.url | relative_url }}"
|
|
156
|
+
class="recommendation-card__link">
|
|
157
|
+
{{ post.title }}
|
|
158
|
+
</a>
|
|
159
|
+
</h4>
|
|
160
|
+
|
|
161
|
+
{%- if post.excerpt -%}
|
|
162
|
+
<p class="recommendation-card__excerpt" itemprop="description">
|
|
163
|
+
{{ post.excerpt | strip_html | truncatewords: 20 }}
|
|
164
|
+
</p>
|
|
165
|
+
{%- endif -%}
|
|
166
|
+
|
|
167
|
+
<div class="recommendation-card__meta">
|
|
168
|
+
{%- if post.date -%}
|
|
169
|
+
<time class="recommendation-card__date"
|
|
170
|
+
datetime="{{ post.date | date_to_xmlschema }}"
|
|
171
|
+
itemprop="datePublished">
|
|
172
|
+
{{ post.date | date: "%B %d, %Y" }}
|
|
173
|
+
</time>
|
|
174
|
+
{%- endif -%}
|
|
175
|
+
|
|
176
|
+
{%- if post.difficulty -%}
|
|
177
|
+
<span class="recommendation-card__separator" aria-hidden="true">•</span>
|
|
178
|
+
{% include components/difficulty-badge.html level=post.difficulty %}
|
|
179
|
+
{%- endif -%}
|
|
180
|
+
|
|
181
|
+
{%- comment -%} Reading time {%- endcomment -%}
|
|
182
|
+
<span class="recommendation-card__separator" aria-hidden="true">•</span>
|
|
183
|
+
{% include components/reading-time.html content=post.content variant="inline" show_icon=false %}
|
|
184
|
+
|
|
185
|
+
{%- if show_score -%}
|
|
186
|
+
<span class="recommendation-card__separator" aria-hidden="true">•</span>
|
|
187
|
+
<span class="recommendation-card__score" aria-label="Similarity score">
|
|
188
|
+
Score: {{ post_score }}
|
|
189
|
+
</span>
|
|
190
|
+
{%- endif -%}
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{%- if post.tags.size > 0 -%}
|
|
194
|
+
<div class="recommendation-card__tags">
|
|
195
|
+
{%- for tag in post.tags limit: 3 -%}
|
|
196
|
+
<span class="recommendation-card__tag">{{ tag }}</span>
|
|
197
|
+
{%- endfor -%}
|
|
198
|
+
</div>
|
|
199
|
+
{%- endif -%}
|
|
200
|
+
</div>
|
|
201
|
+
</article>
|
|
202
|
+
{%- endfor -%}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{%- comment -%} View all link {%- endcomment -%}
|
|
206
|
+
{%- if site.phase3_features.recommendations.show_view_all -%}
|
|
207
|
+
<div class="content-recommendations__footer">
|
|
208
|
+
<a href="{{ '/archive/' | relative_url }}"
|
|
209
|
+
class="content-recommendations__view-all">
|
|
210
|
+
View all articles
|
|
211
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
212
|
+
width="16"
|
|
213
|
+
height="16"
|
|
214
|
+
viewBox="0 0 24 24"
|
|
215
|
+
fill="none"
|
|
216
|
+
stroke="currentColor"
|
|
217
|
+
stroke-width="2"
|
|
218
|
+
stroke-linecap="round"
|
|
219
|
+
stroke-linejoin="round"
|
|
220
|
+
aria-hidden="true">
|
|
221
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
222
|
+
<polyline points="12 5 19 12 12 19"></polyline>
|
|
223
|
+
</svg>
|
|
224
|
+
</a>
|
|
225
|
+
</div>
|
|
226
|
+
{%- endif -%}
|
|
227
|
+
</aside>
|
|
228
|
+
{%- endif -%}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Visual difficulty badge for posts
|
|
3
|
+
Parameters:
|
|
4
|
+
- level: Difficulty level (beginner, intermediate, advanced, expert)
|
|
5
|
+
- show_icon: Show difficulty icon (default: true)
|
|
6
|
+
- show_description: Show difficulty description (default: false)
|
|
7
|
+
{%- endcomment -%}
|
|
8
|
+
|
|
9
|
+
{%- assign difficulty_level = include.level | default: page.difficulty | default: "beginner" | downcase -%}
|
|
10
|
+
{%- assign show_icon = include.show_icon | default: true -%}
|
|
11
|
+
{%- assign show_description = include.show_description | default: false -%}
|
|
12
|
+
|
|
13
|
+
{%- comment -%} Normalize difficulty levels {%- endcomment -%}
|
|
14
|
+
{%- case difficulty_level -%}
|
|
15
|
+
{%- when "beginner" or "easy" or "introductory" or "basic" -%}
|
|
16
|
+
{%- assign normalized_level = "beginner" -%}
|
|
17
|
+
{%- assign level_label = "Beginner" -%}
|
|
18
|
+
{%- assign level_description = "Suitable for those new to the topic" -%}
|
|
19
|
+
{%- assign level_color = "green" -%}
|
|
20
|
+
{%- when "intermediate" or "moderate" or "medium" -%}
|
|
21
|
+
{%- assign normalized_level = "intermediate" -%}
|
|
22
|
+
{%- assign level_label = "Intermediate" -%}
|
|
23
|
+
{%- assign level_description = "Requires some foundational knowledge" -%}
|
|
24
|
+
{%- assign level_color = "blue" -%}
|
|
25
|
+
{%- when "advanced" or "hard" or "difficult" -%}
|
|
26
|
+
{%- assign normalized_level = "advanced" -%}
|
|
27
|
+
{%- assign level_label = "Advanced" -%}
|
|
28
|
+
{%- assign level_description = "For experienced practitioners" -%}
|
|
29
|
+
{%- assign level_color = "orange" -%}
|
|
30
|
+
{%- when "expert" or "very advanced" or "research" -%}
|
|
31
|
+
{%- assign normalized_level = "expert" -%}
|
|
32
|
+
{%- assign level_label = "Expert" -%}
|
|
33
|
+
{%- assign level_description = "Cutting-edge or highly specialized" -%}
|
|
34
|
+
{%- assign level_color = "red" -%}
|
|
35
|
+
{%- else -%}
|
|
36
|
+
{%- assign normalized_level = "beginner" -%}
|
|
37
|
+
{%- assign level_label = "Beginner" -%}
|
|
38
|
+
{%- assign level_description = "Suitable for those new to the topic" -%}
|
|
39
|
+
{%- assign level_color = "green" -%}
|
|
40
|
+
{%- endcase -%}
|
|
41
|
+
|
|
42
|
+
<span class="difficulty-badge difficulty-badge--{{ normalized_level }}"
|
|
43
|
+
data-difficulty="{{ normalized_level }}"
|
|
44
|
+
aria-label="Difficulty level: {{ level_label }}"
|
|
45
|
+
title="{{ level_description }}">
|
|
46
|
+
{%- if show_icon -%}
|
|
47
|
+
<svg class="difficulty-badge__icon"
|
|
48
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
49
|
+
width="16"
|
|
50
|
+
height="16"
|
|
51
|
+
viewBox="0 0 24 24"
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
stroke-width="2"
|
|
55
|
+
stroke-linecap="round"
|
|
56
|
+
stroke-linejoin="round"
|
|
57
|
+
aria-hidden="true">
|
|
58
|
+
{%- case normalized_level -%}
|
|
59
|
+
{%- when "beginner" -%}
|
|
60
|
+
{%- comment -%} Single bar {%- endcomment -%}
|
|
61
|
+
<rect x="3" y="14" width="4" height="7" rx="1"></rect>
|
|
62
|
+
<rect x="10" y="14" width="4" height="7" rx="1" opacity="0.3"></rect>
|
|
63
|
+
<rect x="17" y="14" width="4" height="7" rx="1" opacity="0.3"></rect>
|
|
64
|
+
{%- when "intermediate" -%}
|
|
65
|
+
{%- comment -%} Two bars {%- endcomment -%}
|
|
66
|
+
<rect x="3" y="14" width="4" height="7" rx="1"></rect>
|
|
67
|
+
<rect x="10" y="10" width="4" height="11" rx="1"></rect>
|
|
68
|
+
<rect x="17" y="10" width="4" height="11" rx="1" opacity="0.3"></rect>
|
|
69
|
+
{%- when "advanced" -%}
|
|
70
|
+
{%- comment -%} Three bars {%- endcomment -%}
|
|
71
|
+
<rect x="3" y="14" width="4" height="7" rx="1"></rect>
|
|
72
|
+
<rect x="10" y="10" width="4" height="11" rx="1"></rect>
|
|
73
|
+
<rect x="17" y="6" width="4" height="15" rx="1"></rect>
|
|
74
|
+
{%- when "expert" -%}
|
|
75
|
+
{%- comment -%} Star icon {%- endcomment -%}
|
|
76
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
77
|
+
{%- endcase -%}
|
|
78
|
+
</svg>
|
|
79
|
+
{%- endif -%}
|
|
80
|
+
<span class="difficulty-badge__label">{{ level_label }}</span>
|
|
81
|
+
{%- if show_description -%}
|
|
82
|
+
<span class="difficulty-badge__description">{{ level_description }}</span>
|
|
83
|
+
{%- endif -%}
|
|
84
|
+
</span>
|