datalog-theme 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +125 -0
  3. data/CITATION.cff +2 -2
  4. data/README.md +23 -17
  5. data/_data/cdn-integrity.yml +0 -30
  6. data/_includes/analytics/dashboard.html +3 -1
  7. data/_includes/components/api-function.html +20 -1
  8. data/_includes/components/enhanced-code-block.html +1 -1
  9. data/_includes/csp-meta.html +115 -11
  10. data/_includes/footer.html +19 -20
  11. data/_includes/head.html +60 -23
  12. data/_includes/header/navigation.html +12 -15
  13. data/_includes/header.html +28 -19
  14. data/_includes/layouts/default/article.html +5 -1
  15. data/_includes/meta/math-config.html +9 -5
  16. data/_includes/meta/schema.html +5 -2
  17. data/_includes/meta/scripts-loader.html +16 -32
  18. data/_includes/post/related-posts.html +4 -7
  19. data/_includes/search/index-data.json +9 -34
  20. data/_layouts/dataset.html +1 -0
  21. data/_layouts/default.html +16 -8
  22. data/_layouts/notebook.html +1 -0
  23. data/_layouts/package.html +1 -0
  24. data/_layouts/portfolio.html +1 -0
  25. data/_layouts/post.html +20 -5
  26. data/_layouts/project.html +2 -1
  27. data/_plugins/analytics_dashboard.rb +9 -3
  28. data/_plugins/config_validator.rb +12 -7
  29. data/_plugins/csp_generator.rb +18 -28
  30. data/_plugins/datalog_bibliography.rb +9 -7
  31. data/_plugins/datalog_comments.rb +8 -5
  32. data/_plugins/datalog_slides.rb +9 -8
  33. data/_plugins/i18n.rb +7 -8
  34. data/_plugins/image_optimizer.rb +17 -6
  35. data/_plugins/math_preprocessor.rb +33 -3
  36. data/_plugins/notebook_converter.rb +23 -4
  37. data/_plugins/plugin_loader.rb +3 -1
  38. data/_plugins/publications_generator.rb +8 -2
  39. data/_plugins/rouge_highlight_filter.rb +42 -0
  40. data/_plugins/search_code_blocks.rb +30 -0
  41. data/_plugins/search_normalizer.rb +15 -53
  42. data/_plugins/search_pages.rb +3 -4
  43. data/_sass/_academic-dashboard.scss +262 -0
  44. data/_sass/_base.scss +12 -0
  45. data/_sass/_components.scss +64 -1153
  46. data/_sass/_features.scss +17 -0
  47. data/_sass/_layout.scss +1 -1
  48. data/_sass/_notebooks.scss +322 -0
  49. data/_sass/_open-science-badges.scss +56 -0
  50. data/_sass/{_phase1-enhancements.scss → _post-components.scss} +1 -1
  51. data/_sass/_search-page.scss +530 -0
  52. data/_sass/_search.scss +46 -0
  53. data/_sass/_syntax-highlighting.scss +212 -97
  54. data/_sass/_theme.scss +29 -19
  55. data/_sass/_typography.scss +14 -0
  56. data/assets/css/main.scss +14 -0
  57. data/assets/js/dist/academic.js +1 -1
  58. data/assets/js/dist/analytics-dashboard.js +1 -1
  59. data/assets/js/dist/chunks/chunk-225H5YXE.js +1 -0
  60. data/assets/js/dist/core.js +1 -1
  61. data/assets/js/dist/loader.js +1 -1
  62. data/assets/js/dist/notebook.js +1 -1
  63. data/assets/js/dist/search.js +1 -1
  64. data/assets/js/dist/visualizations.js +11 -2
  65. data/assets/js/loader.js +3 -1
  66. data/datalog-theme.gemspec +36 -23
  67. data/lib/datalog/cli.rb +34 -14
  68. data/lib/datalog/plugin_system/dependency_resolver.rb +0 -2
  69. data/lib/datalog/theme/package.rb +57 -0
  70. data/lib/datalog/theme/repository_checkout.rb +90 -0
  71. data/lib/datalog/theme/version.rb +1 -1
  72. data/lib/datalog/warning_filter.rb +5 -11
  73. data/lib/datalog-theme.rb +5 -0
  74. metadata +48 -133
  75. data/_data/academic.yml +0 -217
  76. data/_data/config/author.yml +0 -121
  77. data/_data/datasets.yml +0 -28
  78. data/_data/js_meta.json +0 -371
  79. data/_data/navigation.yml +0 -145
  80. data/_data/projects.yml +0 -41
  81. data/_data/publications.yml +0 -28
  82. data/_data/social.yml +0 -73
  83. data/_data/visualizations.yml +0 -51
  84. data/_includes/components/advanced-search.html +0 -682
  85. data/_includes/components/bookmark-system.html +0 -96
  86. data/_includes/components/comments.html +0 -244
  87. data/_includes/components/content-recommendations.html +0 -228
  88. data/_includes/components/email-preferences.html +0 -200
  89. data/_includes/components/enhanced-metadata.html +0 -228
  90. data/_includes/components/language-switcher.html +0 -396
  91. data/_includes/components/navigation-enhancements.html +0 -454
  92. data/_includes/components/newsletter-signup.html +0 -178
  93. data/_includes/components/popular-posts.html +0 -233
  94. data/_includes/components/reading-progress.html +0 -133
  95. data/_includes/components/reading-time.html +0 -121
  96. data/_includes/components/series-navigation.html +0 -124
  97. data/_includes/components/social-proof.html +0 -34
  98. data/_includes/components/user-preferences.html +0 -566
  99. data/_includes/meta/syntax-config.html +0 -19
  100. data/_layouts/archive.html +0 -282
  101. data/_layouts/post-sidebar.html +0 -183
  102. data/_sass/_phase3-enhancements.scss +0 -874
  103. data/_sass/_phase4-enhancements.scss +0 -1214
  104. data/_sass/_phase5-enhancements.scss +0 -414
  105. data/assets/js/academic.js +0 -262
  106. data/assets/js/analytics-dashboard.js +0 -382
  107. data/assets/js/core/dark-mode.js +0 -79
  108. data/assets/js/core/github-cards.js +0 -123
  109. data/assets/js/core/language-filter.js +0 -69
  110. data/assets/js/core/navigation.js +0 -184
  111. data/assets/js/core/scroll-progress.js +0 -45
  112. data/assets/js/core/search-hotkeys.js +0 -62
  113. data/assets/js/core/skip-links.js +0 -62
  114. data/assets/js/dist/manifest.json +0 -22
  115. data/assets/js/dist/meta.json +0 -371
  116. data/assets/js/main.js +0 -23
  117. data/assets/js/math.js +0 -818
  118. data/assets/js/notebook.js +0 -158
  119. data/assets/js/search/analytics.js +0 -91
  120. data/assets/js/search/app.js +0 -271
  121. data/assets/js/search/autocomplete.js +0 -120
  122. data/assets/js/search/engine.js +0 -260
  123. data/assets/js/search/filters.js +0 -45
  124. data/assets/js/search/render.js +0 -217
  125. data/assets/js/search/utils.js +0 -99
  126. data/assets/js/search.js +0 -354
  127. data/assets/js/visualizations.js +0 -816
  128. data/assets/publications/datalog-publications.bib +0 -8
  129. data/assets/publications/datalog-publications.ris +0 -9
  130. data/assets/publications/publications.bib +0 -30
  131. data/assets/templates/diogo-ribeiro-cv.md +0 -31
  132. data/assets/templates/diogo-ribeiro-cv.tex +0 -32
  133. data/lib/datalog/theme/theme.rb +0 -18
@@ -1,96 +0,0 @@
1
- {%- comment -%}
2
- Bookmark System - Save posts for later reading
3
- {%- endcomment -%}
4
-
5
- <div class="bookmark-system" data-bookmark-system>
6
- <button type="button"
7
- class="bookmark-button"
8
- data-bookmark-toggle
9
- data-post-id="{{ page.id | slugify }}"
10
- data-post-title="{{ page.title }}"
11
- data-post-url="{{ page.url | relative_url }}"
12
- aria-label="Bookmark this post">
13
- <svg class="bookmark-button__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
14
- <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
15
- </svg>
16
- <span class="bookmark-button__text" data-bookmark-text>Save for later</span>
17
- </button>
18
-
19
- <div class="bookmark-panel" data-bookmark-panel hidden>
20
- <h3>Your Bookmarks</h3>
21
- <div class="bookmark-list" data-bookmark-list></div>
22
- <button type="button" data-clear-bookmarks>Clear all</button>
23
- </div>
24
- </div>
25
-
26
- <script>
27
- (function() {
28
- 'use strict';
29
- const STORAGE_KEY = 'bookmarks';
30
-
31
- class BookmarkSystem {
32
- constructor() {
33
- this.bookmarks = this.loadBookmarks();
34
- this.init();
35
- }
36
-
37
- init() {
38
- const toggleBtn = document.querySelector('[data-bookmark-toggle]');
39
- if (!toggleBtn) return;
40
-
41
- const postId = toggleBtn.dataset.postId;
42
- this.updateButtonState(toggleBtn, postId);
43
-
44
- toggleBtn.addEventListener('click', () => {
45
- const {postId, postTitle, postUrl} = toggleBtn.dataset;
46
- this.toggleBookmark({id: postId, title: postTitle, url: postUrl});
47
- });
48
- }
49
-
50
- toggleBookmark(post) {
51
- const index = this.bookmarks.findIndex(b => b.id === post.id);
52
- if (index > -1) {
53
- this.bookmarks.splice(index, 1);
54
- } else {
55
- this.bookmarks.push({...post, date: new Date().toISOString()});
56
- }
57
- this.saveBookmarks();
58
- this.updateButtonState(document.querySelector('[data-bookmark-toggle]'), post.id);
59
- }
60
-
61
- updateButtonState(btn, postId) {
62
- const isBookmarked = this.bookmarks.some(b => b.id === postId);
63
- const text = btn.querySelector('[data-bookmark-text]');
64
- const icon = btn.querySelector('.bookmark-button__icon');
65
-
66
- if (isBookmarked) {
67
- btn.classList.add('bookmark-button--active');
68
- if (text) text.textContent = 'Saved';
69
- if (icon) icon.style.fill = 'currentColor';
70
- } else {
71
- btn.classList.remove('bookmark-button--active');
72
- if (text) text.textContent = 'Save for later';
73
- if (icon) icon.style.fill = 'none';
74
- }
75
- }
76
-
77
- loadBookmarks() {
78
- try {
79
- return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
80
- } catch (e) {
81
- return [];
82
- }
83
- }
84
-
85
- saveBookmarks() {
86
- try {
87
- localStorage.setItem(STORAGE_KEY, JSON.stringify(this.bookmarks));
88
- } catch (e) {
89
- console.error('Failed to save bookmarks:', e);
90
- }
91
- }
92
- }
93
-
94
- new BookmarkSystem();
95
- })();
96
- </script>
@@ -1,244 +0,0 @@
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 -%}
@@ -1,228 +0,0 @@
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 -%}