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.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +162 -0
  3. data/CITATION.cff +29 -0
  4. data/LICENSE +21 -0
  5. data/README.md +257 -0
  6. data/_data/academic.yml +217 -0
  7. data/_data/cdn-integrity.yml +51 -0
  8. data/_data/config/author.yml +121 -0
  9. data/_data/config/features.yml +262 -0
  10. data/_data/config/site.yml +42 -0
  11. data/_data/config/theme.yml +181 -0
  12. data/_data/datasets.yml +28 -0
  13. data/_data/i18n/en.yml +438 -0
  14. data/_data/i18n/es.yml +438 -0
  15. data/_data/i18n/pt.yml +438 -0
  16. data/_data/js_manifest.json +22 -0
  17. data/_data/js_meta.json +371 -0
  18. data/_data/navigation.yml +145 -0
  19. data/_data/projects.yml +41 -0
  20. data/_data/publications.yml +28 -0
  21. data/_data/social.yml +73 -0
  22. data/_data/visualizations.yml +51 -0
  23. data/_includes/analytics/dashboard.html +149 -0
  24. data/_includes/analytics.html +9 -0
  25. data/_includes/components/academic-dashboard.html +347 -0
  26. data/_includes/components/advanced-search.html +682 -0
  27. data/_includes/components/api-function.html +94 -0
  28. data/_includes/components/author-bio.html +166 -0
  29. data/_includes/components/bookmark-system.html +96 -0
  30. data/_includes/components/breadcrumbs.html +89 -0
  31. data/_includes/components/citation-tools.html +94 -0
  32. data/_includes/components/comments.html +244 -0
  33. data/_includes/components/content-provenance.html +43 -0
  34. data/_includes/components/content-recommendations.html +228 -0
  35. data/_includes/components/difficulty-badge.html +84 -0
  36. data/_includes/components/email-preferences.html +200 -0
  37. data/_includes/components/enhanced-code-block.html +212 -0
  38. data/_includes/components/enhanced-metadata.html +228 -0
  39. data/_includes/components/enhanced-toc.html +209 -0
  40. data/_includes/components/hero.html +15 -0
  41. data/_includes/components/language-switcher.html +396 -0
  42. data/_includes/components/math-fallback.html +19 -0
  43. data/_includes/components/navigation-enhancements.html +454 -0
  44. data/_includes/components/newsletter-signup.html +178 -0
  45. data/_includes/components/open-science-badges.html +16 -0
  46. data/_includes/components/package-install.html +194 -0
  47. data/_includes/components/performance-monitor.html +170 -0
  48. data/_includes/components/popular-posts.html +233 -0
  49. data/_includes/components/post-hero.html +62 -0
  50. data/_includes/components/reading-progress.html +133 -0
  51. data/_includes/components/reading-time.html +121 -0
  52. data/_includes/components/responsive-image.html +63 -0
  53. data/_includes/components/search-facets.html +307 -0
  54. data/_includes/components/series-navigation.html +124 -0
  55. data/_includes/components/social-proof.html +34 -0
  56. data/_includes/components/social-share.html +119 -0
  57. data/_includes/components/user-preferences.html +566 -0
  58. data/_includes/components/visualization-card.html +24 -0
  59. data/_includes/components/viz-table-fallback.html +19 -0
  60. data/_includes/critical-css/default.html +0 -0
  61. data/_includes/critical-css/home.html +0 -0
  62. data/_includes/critical-css/post.html +0 -0
  63. data/_includes/csp-meta.html +17 -0
  64. data/_includes/footer/nav-column.html +31 -0
  65. data/_includes/footer.html +76 -0
  66. data/_includes/head.html +337 -0
  67. data/_includes/header/navigation.html +48 -0
  68. data/_includes/header.html +70 -0
  69. data/_includes/helpers/array-to-sentence.html +21 -0
  70. data/_includes/helpers/date-format.html +16 -0
  71. data/_includes/helpers/link-with-icon.html +26 -0
  72. data/_includes/layouts/default/article.html +67 -0
  73. data/_includes/meta/language-attributes.html +25 -0
  74. data/_includes/meta/math-config.html +21 -0
  75. data/_includes/meta/schema.html +153 -0
  76. data/_includes/meta/scripts-loader.html +52 -0
  77. data/_includes/post/related-posts.html +63 -0
  78. data/_includes/scripts.html +72 -0
  79. data/_includes/skip-link.html +1 -0
  80. data/_includes/toc.html +2 -0
  81. data/_layouts/archive.html +282 -0
  82. data/_layouts/dataset.html +52 -0
  83. data/_layouts/default.html +33 -0
  84. data/_layouts/home.html +48 -0
  85. data/_layouts/notebook.html +173 -0
  86. data/_layouts/package.html +238 -0
  87. data/_layouts/page.html +16 -0
  88. data/_layouts/portfolio.html +41 -0
  89. data/_layouts/post-sidebar.html +183 -0
  90. data/_layouts/post.html +338 -0
  91. data/_layouts/project.html +252 -0
  92. data/_layouts/research.html +504 -0
  93. data/_plugins/analytics_dashboard.rb +315 -0
  94. data/_plugins/config_validator.rb +397 -0
  95. data/_plugins/csp_generator.rb +115 -0
  96. data/_plugins/datalog_bibliography.rb +19 -0
  97. data/_plugins/datalog_comments.rb +18 -0
  98. data/_plugins/datalog_slides.rb +20 -0
  99. data/_plugins/front_matter_compat.rb +109 -0
  100. data/_plugins/i18n.rb +160 -0
  101. data/_plugins/image_optimizer.rb +445 -0
  102. data/_plugins/math_preprocessor.rb +185 -0
  103. data/_plugins/notebook_converter.rb +1107 -0
  104. data/_plugins/plugin_loader.rb +185 -0
  105. data/_plugins/publications_generator.rb +234 -0
  106. data/_plugins/reading_time.rb +15 -0
  107. data/_plugins/search_normalizer.rb +74 -0
  108. data/_plugins/sri_filter.rb +18 -0
  109. data/_plugins/toc_filter.rb +68 -0
  110. data/_plugins/warning_filter.rb +3 -0
  111. data/_sass/_base.scss +142 -0
  112. data/_sass/_components.scss +2380 -0
  113. data/_sass/_header.scss +470 -0
  114. data/_sass/_interactive.scss +344 -0
  115. data/_sass/_layout.scss +1760 -0
  116. data/_sass/_mathematical.scss +368 -0
  117. data/_sass/_mixins.scss +99 -0
  118. data/_sass/_package-docs.scss +715 -0
  119. data/_sass/_phase1-enhancements.scss +721 -0
  120. data/_sass/_phase3-enhancements.scss +874 -0
  121. data/_sass/_phase4-enhancements.scss +1214 -0
  122. data/_sass/_phase5-enhancements.scss +414 -0
  123. data/_sass/_search.scss +654 -0
  124. data/_sass/_syntax-highlighting.scss +128 -0
  125. data/_sass/_theme.scss +79 -0
  126. data/_sass/_typography.scss +148 -0
  127. data/_sass/_utilities.scss +334 -0
  128. data/_sass/_variables.scss +153 -0
  129. data/_sass/_visualizations.scss +242 -0
  130. data/assets/css/main.scss +4 -0
  131. data/assets/img/20220607123041_detail.001.png +0 -0
  132. data/assets/img/favicons/android-chrome-192x192.png +0 -0
  133. data/assets/img/favicons/android-chrome-512x512.png +0 -0
  134. data/assets/img/favicons/apple-touch-icon.png +0 -0
  135. data/assets/img/favicons/favicon-16x16.png +0 -0
  136. data/assets/img/favicons/favicon-32x32.png +0 -0
  137. data/assets/img/favicons/favicon.ico +0 -0
  138. data/assets/img/favicons/site.webmanifest +21 -0
  139. data/assets/img/portfolio-placeholder.svg +20 -0
  140. data/assets/js/academic.js +262 -0
  141. data/assets/js/analytics-dashboard.js +382 -0
  142. data/assets/js/core/dark-mode.js +79 -0
  143. data/assets/js/core/github-cards.js +123 -0
  144. data/assets/js/core/language-filter.js +69 -0
  145. data/assets/js/core/navigation.js +184 -0
  146. data/assets/js/core/scroll-progress.js +45 -0
  147. data/assets/js/core/search-hotkeys.js +62 -0
  148. data/assets/js/core/skip-links.js +62 -0
  149. data/assets/js/loader.js +163 -0
  150. data/assets/js/main.js +23 -0
  151. data/assets/js/math.js +818 -0
  152. data/assets/js/notebook.js +158 -0
  153. data/assets/js/search/analytics.js +91 -0
  154. data/assets/js/search/app.js +271 -0
  155. data/assets/js/search/autocomplete.js +120 -0
  156. data/assets/js/search/engine.js +260 -0
  157. data/assets/js/search/filters.js +38 -0
  158. data/assets/js/search/render.js +217 -0
  159. data/assets/js/search/utils.js +99 -0
  160. data/assets/js/search.js +354 -0
  161. data/assets/js/visualizations.js +816 -0
  162. data/assets/publications/datalog-publications.bib +8 -0
  163. data/assets/publications/datalog-publications.ris +9 -0
  164. data/assets/publications/publications.bib +30 -0
  165. data/assets/templates/diogo-ribeiro-cv.md +31 -0
  166. data/assets/templates/diogo-ribeiro-cv.tex +32 -0
  167. data/bin/datalog +26 -0
  168. data/datalog-theme.gemspec +72 -0
  169. data/lib/datalog/cli.rb +563 -0
  170. data/lib/datalog/plugin_system/dependency_resolver.rb +223 -0
  171. data/lib/datalog/plugin_system.rb +237 -0
  172. data/lib/datalog/plugins/citations.rb +182 -0
  173. data/lib/datalog/plugins/comments.rb +183 -0
  174. data/lib/datalog/plugins/search.rb +45 -0
  175. data/lib/datalog/plugins/slides.rb +121 -0
  176. data/lib/datalog/theme/theme.rb +18 -0
  177. data/lib/datalog/theme/version.rb +7 -0
  178. data/lib/datalog/warning_filter.rb +28 -0
  179. data/lib/datalog-theme.rb +17 -0
  180. metadata +555 -0
@@ -0,0 +1,200 @@
1
+ {%- comment -%}
2
+ Email Subscription Preferences - Granular email controls
3
+ {%- endcomment -%}
4
+
5
+ <div class="email-preferences" data-email-preferences>
6
+ <h3>Email Preferences</h3>
7
+
8
+ <form class="email-preferences__form" data-preferences-form>
9
+ <div class="form-group">
10
+ <label for="email-input">Email Address</label>
11
+ <input type="email" id="email-input" name="email" required placeholder="your@email.com">
12
+ </div>
13
+
14
+ <fieldset class="form-group">
15
+ <legend>Subscription Types</legend>
16
+
17
+ <label class="checkbox-label">
18
+ <input type="checkbox" name="weekly_digest" value="true" checked>
19
+ <span>Weekly Digest</span>
20
+ <small>Best posts from the week, every Monday</small>
21
+ </label>
22
+
23
+ <label class="checkbox-label">
24
+ <input type="checkbox" name="new_posts" value="true">
25
+ <span>New Posts</span>
26
+ <small>Notification for each new post</small>
27
+ </label>
28
+
29
+ <label class="checkbox-label">
30
+ <input type="checkbox" name="comments" value="true">
31
+ <span>Comment Replies</span>
32
+ <small>When someone replies to your comments</small>
33
+ </label>
34
+
35
+ <label class="checkbox-label">
36
+ <input type="checkbox" name="newsletter" value="true" checked>
37
+ <span>Newsletter</span>
38
+ <small>Monthly newsletter with curated content</small>
39
+ </label>
40
+ </fieldset>
41
+
42
+ <fieldset class="form-group">
43
+ <legend>Content Preferences</legend>
44
+
45
+ <label class="checkbox-label">
46
+ <input type="checkbox" name="topic_ml" value="true">
47
+ <span>Machine Learning</span>
48
+ </label>
49
+
50
+ <label class="checkbox-label">
51
+ <input type="checkbox" name="topic_statistics" value="true">
52
+ <span>Statistics</span>
53
+ </label>
54
+
55
+ <label class="checkbox-label">
56
+ <input type="checkbox" name="topic_visualization" value="true">
57
+ <span>Data Visualization</span>
58
+ </label>
59
+
60
+ <label class="checkbox-label">
61
+ <input type="checkbox" name="topic_python" value="true">
62
+ <span>Python</span>
63
+ </label>
64
+ </fieldset>
65
+
66
+ <div class="form-group">
67
+ <label for="difficulty-select">Difficulty Level</label>
68
+ <select id="difficulty-select" name="difficulty">
69
+ <option value="all">All Levels</option>
70
+ <option value="beginner">Beginner</option>
71
+ <option value="intermediate">Intermediate</option>
72
+ <option value="advanced">Advanced</option>
73
+ <option value="expert">Expert</option>
74
+ </select>
75
+ </div>
76
+
77
+ <div class="form-actions">
78
+ <button type="submit" class="button button--primary">Save Preferences</button>
79
+ <button type="button" class="button button--secondary" data-unsubscribe>Unsubscribe</button>
80
+ </div>
81
+
82
+ <div class="form-message" data-message hidden></div>
83
+ </form>
84
+ </div>
85
+
86
+ <script>
87
+ (function() {
88
+ 'use strict';
89
+
90
+ const STORAGE_KEY = 'emailPreferences';
91
+
92
+ class EmailPreferences {
93
+ constructor() {
94
+ this.init();
95
+ }
96
+
97
+ init() {
98
+ const form = document.querySelector('[data-preferences-form]');
99
+ if (!form) return;
100
+
101
+ this.loadPreferences(form);
102
+
103
+ form.addEventListener('submit', (e) => {
104
+ e.preventDefault();
105
+ this.savePreferences(form);
106
+ });
107
+
108
+ const unsubBtn = form.querySelector('[data-unsubscribe]');
109
+ unsubBtn?.addEventListener('click', () => this.unsubscribe(form));
110
+ }
111
+
112
+ loadPreferences(form) {
113
+ try {
114
+ const saved = localStorage.getItem(STORAGE_KEY);
115
+ if (!saved) return;
116
+
117
+ const prefs = JSON.parse(saved);
118
+
119
+ // Set email
120
+ const emailInput = form.querySelector('[name="email"]');
121
+ if (emailInput && prefs.email) {
122
+ emailInput.value = prefs.email;
123
+ }
124
+
125
+ // Set checkboxes
126
+ Object.keys(prefs).forEach(key => {
127
+ const input = form.querySelector(`[name="${key}"]`);
128
+ if (input && input.type === 'checkbox') {
129
+ input.checked = prefs[key] === 'true' || prefs[key] === true;
130
+ } else if (input && input.type !== 'email') {
131
+ input.value = prefs[key];
132
+ }
133
+ });
134
+ } catch (e) {
135
+ console.error('Failed to load preferences:', e);
136
+ }
137
+ }
138
+
139
+ savePreferences(form) {
140
+ const formData = new FormData(form);
141
+ const prefs = {};
142
+
143
+ for (const [key, value] of formData.entries()) {
144
+ prefs[key] = value;
145
+ }
146
+
147
+ // Save checkboxes that are unchecked
148
+ form.querySelectorAll('input[type="checkbox"]').forEach(cb => {
149
+ if (!cb.checked) {
150
+ prefs[cb.name] = 'false';
151
+ }
152
+ });
153
+
154
+ try {
155
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
156
+ this.showMessage(form, 'Preferences saved successfully!', 'success');
157
+
158
+ // In production, send to backend
159
+ // fetch('/api/email-preferences', {
160
+ // method: 'POST',
161
+ // headers: {'Content-Type': 'application/json'},
162
+ // body: JSON.stringify(prefs)
163
+ // });
164
+ } catch (e) {
165
+ this.showMessage(form, 'Failed to save preferences', 'error');
166
+ }
167
+ }
168
+
169
+ unsubscribe(form) {
170
+ if (confirm('Are you sure you want to unsubscribe from all emails?')) {
171
+ try {
172
+ localStorage.removeItem(STORAGE_KEY);
173
+ form.reset();
174
+ this.showMessage(form, 'You have been unsubscribed', 'success');
175
+
176
+ // In production, send to backend
177
+ // fetch('/api/unsubscribe', {method: 'POST'});
178
+ } catch (e) {
179
+ this.showMessage(form, 'Failed to unsubscribe', 'error');
180
+ }
181
+ }
182
+ }
183
+
184
+ showMessage(form, text, type) {
185
+ const messageEl = form.querySelector('[data-message]');
186
+ if (!messageEl) return;
187
+
188
+ messageEl.textContent = text;
189
+ messageEl.className = `form-message form-message--${type}`;
190
+ messageEl.hidden = false;
191
+
192
+ setTimeout(() => {
193
+ messageEl.hidden = true;
194
+ }, 5000);
195
+ }
196
+ }
197
+
198
+ new EmailPreferences();
199
+ })();
200
+ </script>
@@ -0,0 +1,212 @@
1
+ {%- comment -%}
2
+ Enhanced Code Block with Advanced Sharing Options
3
+ Adds copy, download, and playground integration to code blocks
4
+
5
+ Parameters:
6
+ - code: Code content
7
+ - language: Programming language
8
+ - filename: Optional filename for downloads
9
+ - playground_url: URL to online playground (optional)
10
+ - show_line_numbers: Show line numbers (default: true)
11
+ {%- endcomment -%}
12
+
13
+ {%- assign code_content = include.code -%}
14
+ {%- assign language = include.language | default: "text" -%}
15
+ {%- assign filename = include.filename -%}
16
+ {%- assign playground_url = include.playground_url -%}
17
+ {%- assign show_line_numbers = include.show_line_numbers | default: true -%}
18
+
19
+ {%- assign code_id = "code-" | append: language | append: "-" | append: include.index | default: "1" -%}
20
+
21
+ <div class="enhanced-code-block" data-enhanced-code data-language="{{ language }}">
22
+ <div class="enhanced-code-block__header">
23
+ <div class="enhanced-code-block__info">
24
+ <span class="enhanced-code-block__language">{{ language | upcase }}</span>
25
+ {%- if filename -%}
26
+ <span class="enhanced-code-block__filename">{{ filename }}</span>
27
+ {%- endif -%}
28
+ </div>
29
+
30
+ <div class="enhanced-code-block__actions">
31
+ {%- comment -%} Copy button {%- endcomment -%}
32
+ <button type="button"
33
+ class="enhanced-code-block__action"
34
+ data-action="copy"
35
+ data-code-id="{{ code_id }}"
36
+ aria-label="Copy code to clipboard"
37
+ title="Copy code">
38
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
39
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
40
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
41
+ </svg>
42
+ <span class="enhanced-code-block__action-text">Copy</span>
43
+ </button>
44
+
45
+ {%- comment -%} Download button {%- endcomment -%}
46
+ <button type="button"
47
+ class="enhanced-code-block__action"
48
+ data-action="download"
49
+ data-code-id="{{ code_id }}"
50
+ data-filename="{{ filename | default: 'code.' | append: language }}"
51
+ aria-label="Download code file"
52
+ title="Download">
53
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
54
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
55
+ <polyline points="7 10 12 15 17 10"></polyline>
56
+ <line x1="12" y1="15" x2="12" y2="3"></line>
57
+ </svg>
58
+ <span class="enhanced-code-block__action-text">Download</span>
59
+ </button>
60
+
61
+ {%- comment -%} Playground button {%- endcomment -%}
62
+ {%- if playground_url -%}
63
+ <a href="{{ playground_url }}"
64
+ class="enhanced-code-block__action"
65
+ target="_blank"
66
+ rel="noopener noreferrer"
67
+ aria-label="Open in playground"
68
+ title="Open in playground">
69
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
70
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
71
+ </svg>
72
+ <span class="enhanced-code-block__action-text">Run</span>
73
+ </a>
74
+ {%- elsif language == "python" or language == "javascript" or language == "typescript" -%}
75
+ <button type="button"
76
+ class="enhanced-code-block__action"
77
+ data-action="playground"
78
+ data-code-id="{{ code_id }}"
79
+ aria-label="Open in online playground"
80
+ title="Open in playground">
81
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
82
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
83
+ </svg>
84
+ <span class="enhanced-code-block__action-text">Run</span>
85
+ </button>
86
+ {%- endif -%}
87
+
88
+ {%- comment -%} Share button {%- endcomment -%}
89
+ <button type="button"
90
+ class="enhanced-code-block__action"
91
+ data-action="share"
92
+ data-code-id="{{ code_id }}"
93
+ aria-label="Share code snippet"
94
+ title="Share snippet">
95
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
96
+ <circle cx="18" cy="5" r="3"></circle>
97
+ <circle cx="6" cy="12" r="3"></circle>
98
+ <circle cx="18" cy="19" r="3"></circle>
99
+ <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
100
+ <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
101
+ </svg>
102
+ <span class="enhanced-code-block__action-text">Share</span>
103
+ </button>
104
+ </div>
105
+ </div>
106
+
107
+ <pre class="enhanced-code-block__pre{% if show_line_numbers %} enhanced-code-block__pre--numbered{% endif %}"><code id="{{ code_id }}" class="language-{{ language }}" data-code-content>{{ code_content }}</code></pre>
108
+
109
+ {%- comment -%} Feedback message {%- endcomment -%}
110
+ <div class="enhanced-code-block__feedback" data-feedback role="status" aria-live="polite"></div>
111
+ </div>
112
+
113
+ <script>
114
+ (function() {
115
+ 'use strict';
116
+
117
+ // Playground URLs for different languages
118
+ const playgroundUrls = {
119
+ python: 'https://repl.it/languages/python3',
120
+ javascript: 'https://jsfiddle.net/',
121
+ typescript: 'https://www.typescriptlang.org/play',
122
+ go: 'https://go.dev/play/',
123
+ rust: 'https://play.rust-lang.org/',
124
+ sql: 'https://www.db-fiddle.com/'
125
+ };
126
+
127
+ document.addEventListener('click', function(e) {
128
+ const button = e.target.closest('[data-action]');
129
+ if (!button) return;
130
+
131
+ const action = button.dataset.action;
132
+ const codeId = button.dataset.codeId;
133
+ const codeElement = document.getElementById(codeId);
134
+ if (!codeElement) return;
135
+
136
+ const code = codeElement.textContent;
137
+ const codeBlock = button.closest('[data-enhanced-code]');
138
+ const feedback = codeBlock.querySelector('[data-feedback]');
139
+
140
+ function showFeedback(message, type = 'success') {
141
+ if (feedback) {
142
+ feedback.textContent = message;
143
+ feedback.className = `enhanced-code-block__feedback enhanced-code-block__feedback--${type}`;
144
+ setTimeout(() => {
145
+ feedback.textContent = '';
146
+ feedback.className = 'enhanced-code-block__feedback';
147
+ }, 3000);
148
+ }
149
+ }
150
+
151
+ switch (action) {
152
+ case 'copy':
153
+ if (navigator.clipboard) {
154
+ navigator.clipboard.writeText(code).then(() => {
155
+ showFeedback('Code copied to clipboard!');
156
+ button.querySelector('.enhanced-code-block__action-text').textContent = 'Copied!';
157
+ setTimeout(() => {
158
+ button.querySelector('.enhanced-code-block__action-text').textContent = 'Copy';
159
+ }, 2000);
160
+ }).catch(() => {
161
+ showFeedback('Failed to copy code', 'error');
162
+ });
163
+ }
164
+ break;
165
+
166
+ case 'download':
167
+ const filename = button.dataset.filename || 'code.txt';
168
+ const blob = new Blob([code], { type: 'text/plain' });
169
+ const url = URL.createObjectURL(blob);
170
+ const link = document.createElement('a');
171
+ link.href = url;
172
+ link.download = filename;
173
+ link.click();
174
+ URL.revokeObjectURL(url);
175
+ showFeedback(`Downloaded as ${filename}`);
176
+ break;
177
+
178
+ case 'playground':
179
+ const language = codeBlock.dataset.language;
180
+ const playgroundUrl = playgroundUrls[language];
181
+ if (playgroundUrl) {
182
+ // Encode code and open in new window
183
+ window.open(playgroundUrl, '_blank', 'noopener,noreferrer');
184
+ showFeedback('Opening in playground...');
185
+ } else {
186
+ showFeedback('Playground not available for this language', 'error');
187
+ }
188
+ break;
189
+
190
+ case 'share':
191
+ if (navigator.share) {
192
+ navigator.share({
193
+ title: 'Code Snippet',
194
+ text: code
195
+ }).then(() => {
196
+ showFeedback('Shared successfully!');
197
+ }).catch((err) => {
198
+ if (err.name !== 'AbortError') {
199
+ showFeedback('Failed to share', 'error');
200
+ }
201
+ });
202
+ } else {
203
+ // Fallback: copy to clipboard
204
+ navigator.clipboard.writeText(code).then(() => {
205
+ showFeedback('Link copied to clipboard!');
206
+ });
207
+ }
208
+ break;
209
+ }
210
+ });
211
+ })();
212
+ </script>
@@ -0,0 +1,228 @@
1
+ {%- comment -%}
2
+ Enhanced Metadata Display Component
3
+ Displays comprehensive post metadata including last updated, related tags, and more
4
+
5
+ Parameters:
6
+ - show_updated_date: Show last updated date (default: true)
7
+ - show_tags_cloud: Show related tags cloud (default: true)
8
+ - show_category_path: Show category breadcrumb (default: true)
9
+ - show_reading_level: Show reading level indicator (default: true)
10
+ - show_word_count: Show detailed word count (default: true)
11
+ - layout: Display layout (inline, stacked, card) (default: inline)
12
+ {%- endcomment -%}
13
+
14
+ {%- assign show_updated_date = include.show_updated_date | default: true -%}
15
+ {%- assign show_tags_cloud = include.show_tags_cloud | default: true -%}
16
+ {%- assign show_category_path = include.show_category_path | default: true -%}
17
+ {%- assign show_reading_level = include.show_reading_level | default: true -%}
18
+ {%- assign show_word_count = include.show_word_count | default: true -%}
19
+ {%- assign layout = include.layout | default: "inline" -%}
20
+
21
+ {%- comment -%} Calculate metadata {%- endcomment -%}
22
+ {%- assign word_count = page.content | strip_html | number_of_words -%}
23
+ {%- assign reading_time = word_count | divided_by: 200 | ceil -%}
24
+
25
+ {%- comment -%} Determine reading level {%- endcomment -%}
26
+ {%- if word_count < 500 -%}
27
+ {%- assign reading_level = "Quick Read" -%}
28
+ {%- assign level_class = "quick" -%}
29
+ {%- elsif word_count < 1500 -%}
30
+ {%- assign reading_level = "Standard" -%}
31
+ {%- assign level_class = "standard" -%}
32
+ {%- elsif word_count < 3000 -%}
33
+ {%- assign reading_level = "In-Depth" -%}
34
+ {%- assign level_class = "in-depth" -%}
35
+ {%- else -%}
36
+ {%- assign reading_level = "Comprehensive" -%}
37
+ {%- assign level_class = "comprehensive" -%}
38
+ {%- endif -%}
39
+
40
+ {%- comment -%} Check for last updated date {%- endcomment -%}
41
+ {%- assign has_updated_date = false -%}
42
+ {%- if page.last_modified_at or page.updated_date -%}
43
+ {%- assign has_updated_date = true -%}
44
+ {%- assign updated_date = page.last_modified_at | default: page.updated_date -%}
45
+ {%- endif -%}
46
+
47
+ <div class="enhanced-metadata enhanced-metadata--{{ layout }}"
48
+ data-enhanced-metadata
49
+ itemscope
50
+ itemtype="https://schema.org/Article">
51
+
52
+ <div class="enhanced-metadata__primary">
53
+ {%- comment -%} Published date {%- endcomment -%}
54
+ <div class="metadata-item metadata-item--date">
55
+ <svg class="metadata-item__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
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
65
+ <line x1="16" y1="2" x2="16" y2="6"></line>
66
+ <line x1="8" y1="2" x2="8" y2="6"></line>
67
+ <line x1="3" y1="10" x2="21" y2="10"></line>
68
+ </svg>
69
+ <span class="metadata-item__label">Published</span>
70
+ <time class="metadata-item__value"
71
+ datetime="{{ page.date | date_to_xmlschema }}"
72
+ itemprop="datePublished">
73
+ {{ page.date | date: "%B %d, %Y" }}
74
+ </time>
75
+ </div>
76
+
77
+ {%- comment -%} Updated date {%- endcomment -%}
78
+ {%- if show_updated_date and has_updated_date -%}
79
+ <div class="metadata-item metadata-item--updated">
80
+ <svg class="metadata-item__icon"
81
+ xmlns="http://www.w3.org/2000/svg"
82
+ width="16"
83
+ height="16"
84
+ viewBox="0 0 24 24"
85
+ fill="none"
86
+ stroke="currentColor"
87
+ stroke-width="2"
88
+ aria-hidden="true">
89
+ <polyline points="1 4 1 10 7 10"></polyline>
90
+ <polyline points="23 20 23 14 17 14"></polyline>
91
+ <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
92
+ </svg>
93
+ <span class="metadata-item__label">Updated</span>
94
+ <time class="metadata-item__value"
95
+ datetime="{{ updated_date | date_to_xmlschema }}"
96
+ itemprop="dateModified">
97
+ {{ updated_date | date: "%B %d, %Y" }}
98
+ </time>
99
+ </div>
100
+ {%- endif -%}
101
+
102
+ {%- comment -%} Author {%- endcomment -%}
103
+ {%- if page.author -%}
104
+ <div class="metadata-item metadata-item--author"
105
+ itemprop="author"
106
+ itemscope
107
+ itemtype="https://schema.org/Person">
108
+ <svg class="metadata-item__icon"
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
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
118
+ <circle cx="12" cy="7" r="4"></circle>
119
+ </svg>
120
+ <span class="metadata-item__label">By</span>
121
+ <span class="metadata-item__value" itemprop="name">{{ page.author }}</span>
122
+ </div>
123
+ {%- endif -%}
124
+
125
+ {%- comment -%} Word count and reading time {%- endcomment -%}
126
+ {%- if show_word_count -%}
127
+ <div class="metadata-item metadata-item--words">
128
+ <svg class="metadata-item__icon"
129
+ xmlns="http://www.w3.org/2000/svg"
130
+ width="16"
131
+ height="16"
132
+ viewBox="0 0 24 24"
133
+ fill="none"
134
+ stroke="currentColor"
135
+ stroke-width="2"
136
+ aria-hidden="true">
137
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
138
+ <polyline points="14 2 14 8 20 8"></polyline>
139
+ <line x1="16" y1="13" x2="8" y2="13"></line>
140
+ <line x1="16" y1="17" x2="8" y2="17"></line>
141
+ <polyline points="10 9 9 9 8 9"></polyline>
142
+ </svg>
143
+ <span class="metadata-item__value">
144
+ {{ word_count | divided_by: 1000 }},{{ word_count | modulo: 1000 | divided_by: 100 }}{{ word_count | modulo: 100 | divided_by: 10 }}{{ word_count | modulo: 10 }} words
145
+ </span>
146
+ <span class="metadata-item__separator">·</span>
147
+ <span class="metadata-item__value"
148
+ itemprop="timeRequired"
149
+ content="PT{{ reading_time }}M">
150
+ {{ reading_time }} min read
151
+ </span>
152
+ </div>
153
+ {%- endif -%}
154
+
155
+ {%- comment -%} Reading level {%- endcomment -%}
156
+ {%- if show_reading_level -%}
157
+ <div class="metadata-item metadata-item--level">
158
+ <span class="reading-level reading-level--{{ level_class }}">
159
+ {{ reading_level }}
160
+ </span>
161
+ </div>
162
+ {%- endif -%}
163
+ </div>
164
+
165
+ {%- comment -%} Category path {%- endcomment -%}
166
+ {%- if show_category_path and page.categories.size > 0 -%}
167
+ <div class="enhanced-metadata__categories">
168
+ <svg class="enhanced-metadata__categories-icon"
169
+ xmlns="http://www.w3.org/2000/svg"
170
+ width="14"
171
+ height="14"
172
+ viewBox="0 0 24 24"
173
+ fill="none"
174
+ stroke="currentColor"
175
+ stroke-width="2"
176
+ aria-hidden="true">
177
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
178
+ </svg>
179
+ <nav class="category-path" aria-label="Categories">
180
+ {%- for category in page.categories -%}
181
+ {%- unless forloop.first -%}
182
+ <span class="category-path__separator" aria-hidden="true">/</span>
183
+ {%- endunless -%}
184
+ <a href="{{ '/categories/' | append: category | slugify | append: '/' | relative_url }}"
185
+ class="category-path__link">
186
+ {{ category }}
187
+ </a>
188
+ {%- endfor -%}
189
+ </nav>
190
+ </div>
191
+ {%- endif -%}
192
+
193
+ {%- comment -%} Tags cloud {%- endcomment -%}
194
+ {%- if show_tags_cloud and page.tags.size > 0 -%}
195
+ <div class="enhanced-metadata__tags">
196
+ <svg class="enhanced-metadata__tags-icon"
197
+ xmlns="http://www.w3.org/2000/svg"
198
+ width="14"
199
+ height="14"
200
+ viewBox="0 0 24 24"
201
+ fill="none"
202
+ stroke="currentColor"
203
+ stroke-width="2"
204
+ aria-hidden="true">
205
+ <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
206
+ <line x1="7" y1="7" x2="7.01" y2="7"></line>
207
+ </svg>
208
+ <div class="tags-cloud" aria-label="Related tags">
209
+ {%- for tag in page.tags -%}
210
+ <a href="{{ '/tags/' | append: tag | slugify | append: '/' | relative_url }}"
211
+ class="tag-pill"
212
+ rel="tag">
213
+ {{ tag }}
214
+ </a>
215
+ {%- endfor -%}
216
+ </div>
217
+ </div>
218
+ {%- endif -%}
219
+
220
+ {%- comment -%} Additional structured data {%- endcomment -%}
221
+ <meta itemprop="wordCount" content="{{ word_count }}">
222
+ {%- if page.description -%}
223
+ <meta itemprop="description" content="{{ page.description | strip_html | truncate: 160 }}">
224
+ {%- endif -%}
225
+ {%- if page.featured_image or page.image -%}
226
+ <meta itemprop="image" content="{{ page.featured_image | default: page.image | absolute_url }}">
227
+ {%- endif -%}
228
+ </div>