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,194 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Package Installation Component
|
|
3
|
+
Displays installation instructions for different package managers
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- language: python, r, julia, etc.
|
|
7
|
+
- package_name: Name of the package
|
|
8
|
+
- pip_command: Custom pip install command
|
|
9
|
+
- conda_command: Custom conda install command
|
|
10
|
+
- cran_command: Custom R install command
|
|
11
|
+
- git_url: GitHub clone URL
|
|
12
|
+
{%- endcomment -%}
|
|
13
|
+
|
|
14
|
+
{%- assign language = include.language | default: "python" -%}
|
|
15
|
+
{%- assign package_name = include.package_name | default: page.title -%}
|
|
16
|
+
{%- assign pip_command = include.pip_command -%}
|
|
17
|
+
{%- assign conda_command = include.conda_command -%}
|
|
18
|
+
{%- assign cran_command = include.cran_command -%}
|
|
19
|
+
{%- assign git_url = include.git_url | default: page.github_url -%}
|
|
20
|
+
|
|
21
|
+
<div class="package-install" id="installation">
|
|
22
|
+
<h2 class="package-install__title">Installation</h2>
|
|
23
|
+
|
|
24
|
+
{%- if language == "python" -%}
|
|
25
|
+
<div class="package-install__tabs" data-install-tabs>
|
|
26
|
+
<div class="package-install__tab-buttons">
|
|
27
|
+
<button class="package-install__tab-btn package-install__tab-btn--active"
|
|
28
|
+
data-tab="pip">
|
|
29
|
+
pip
|
|
30
|
+
</button>
|
|
31
|
+
<button class="package-install__tab-btn" data-tab="conda">
|
|
32
|
+
conda
|
|
33
|
+
</button>
|
|
34
|
+
<button class="package-install__tab-btn" data-tab="git">
|
|
35
|
+
Git
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="package-install__tab-content" data-tab-content="pip">
|
|
40
|
+
<div class="package-install__code-block">
|
|
41
|
+
<div class="package-install__code-header">
|
|
42
|
+
<span class="package-install__code-label">Terminal</span>
|
|
43
|
+
<button class="package-install__copy-btn" data-copy-btn>
|
|
44
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
45
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
46
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
47
|
+
</svg>
|
|
48
|
+
Copy
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
<pre><code data-copy-text>{{ pip_command | default: "pip install " | append: package_name }}</code></pre>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="package-install__tab-content" data-tab-content="conda" hidden>
|
|
56
|
+
<div class="package-install__code-block">
|
|
57
|
+
<div class="package-install__code-header">
|
|
58
|
+
<span class="package-install__code-label">Terminal</span>
|
|
59
|
+
<button class="package-install__copy-btn" data-copy-btn>
|
|
60
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
61
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
62
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
63
|
+
</svg>
|
|
64
|
+
Copy
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
<pre><code data-copy-text>{{ conda_command | default: "conda install " | append: package_name }}</code></pre>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="package-install__tab-content" data-tab-content="git" hidden>
|
|
72
|
+
<div class="package-install__code-block">
|
|
73
|
+
<div class="package-install__code-header">
|
|
74
|
+
<span class="package-install__code-label">Terminal</span>
|
|
75
|
+
<button class="package-install__copy-btn" data-copy-btn>
|
|
76
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
77
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
78
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
79
|
+
</svg>
|
|
80
|
+
Copy
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
<pre><code data-copy-text>git clone {{ git_url }}
|
|
84
|
+
cd {{ package_name }}
|
|
85
|
+
pip install -e .</code></pre>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{%- elsif language == "r" -%}
|
|
91
|
+
<div class="package-install__tabs" data-install-tabs>
|
|
92
|
+
<div class="package-install__tab-buttons">
|
|
93
|
+
<button class="package-install__tab-btn package-install__tab-btn--active" data-tab="cran">
|
|
94
|
+
CRAN
|
|
95
|
+
</button>
|
|
96
|
+
<button class="package-install__tab-btn" data-tab="github">
|
|
97
|
+
GitHub
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="package-install__tab-content" data-tab-content="cran">
|
|
102
|
+
<div class="package-install__code-block">
|
|
103
|
+
<div class="package-install__code-header">
|
|
104
|
+
<span class="package-install__code-label">R Console</span>
|
|
105
|
+
<button class="package-install__copy-btn" data-copy-btn>
|
|
106
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
107
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
108
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
109
|
+
</svg>
|
|
110
|
+
Copy
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
<pre><code data-copy-text>{{ cran_command | default: "install.packages('" | append: package_name | append: "')" }}</code></pre>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="package-install__tab-content" data-tab-content="github" hidden>
|
|
118
|
+
<div class="package-install__code-block">
|
|
119
|
+
<div class="package-install__code-header">
|
|
120
|
+
<span class="package-install__code-label">R Console</span>
|
|
121
|
+
<button class="package-install__copy-btn" data-copy-btn>
|
|
122
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
123
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
124
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
125
|
+
</svg>
|
|
126
|
+
Copy
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
<pre><code data-copy-text>devtools::install_github("{{ git_url | remove: "https://github.com/" }}")</code></pre>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
{%- endif -%}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
(function() {
|
|
138
|
+
'use strict';
|
|
139
|
+
|
|
140
|
+
const initInstallTabs = () => {
|
|
141
|
+
const containers = document.querySelectorAll('[data-install-tabs]');
|
|
142
|
+
|
|
143
|
+
containers.forEach(container => {
|
|
144
|
+
const tabBtns = container.querySelectorAll('[data-tab]');
|
|
145
|
+
const tabContents = container.querySelectorAll('[data-tab-content]');
|
|
146
|
+
const copyBtns = container.querySelectorAll('[data-copy-btn]');
|
|
147
|
+
|
|
148
|
+
// Tab switching
|
|
149
|
+
tabBtns.forEach(btn => {
|
|
150
|
+
btn.addEventListener('click', () => {
|
|
151
|
+
const tabName = btn.dataset.tab;
|
|
152
|
+
|
|
153
|
+
// Update buttons
|
|
154
|
+
tabBtns.forEach(b => b.classList.remove('package-install__tab-btn--active'));
|
|
155
|
+
btn.classList.add('package-install__tab-btn--active');
|
|
156
|
+
|
|
157
|
+
// Update content
|
|
158
|
+
tabContents.forEach(content => {
|
|
159
|
+
content.hidden = content.dataset.tabContent !== tabName;
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Copy functionality
|
|
165
|
+
copyBtns.forEach(btn => {
|
|
166
|
+
btn.addEventListener('click', async () => {
|
|
167
|
+
const codeEl = btn.closest('.package-install__code-block').querySelector('[data-copy-text]');
|
|
168
|
+
const text = codeEl.textContent.trim();
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await navigator.clipboard.writeText(text);
|
|
172
|
+
const originalHTML = btn.innerHTML;
|
|
173
|
+
btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!';
|
|
174
|
+
btn.classList.add('package-install__copy-btn--success');
|
|
175
|
+
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
btn.innerHTML = originalHTML;
|
|
178
|
+
btn.classList.remove('package-install__copy-btn--success');
|
|
179
|
+
}, 2000);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error('Failed to copy:', err);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
if (document.readyState === 'loading') {
|
|
189
|
+
document.addEventListener('DOMContentLoaded', initInstallTabs);
|
|
190
|
+
} else {
|
|
191
|
+
initInstallTabs();
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
</script>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Performance Monitoring - Track Core Web Vitals and RUM
|
|
3
|
+
{%- endcomment -%}
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
(function() {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
class PerformanceMonitor {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.metrics = {};
|
|
12
|
+
this.init();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
init() {
|
|
16
|
+
// Core Web Vitals
|
|
17
|
+
this.measureCLS();
|
|
18
|
+
this.measureFID();
|
|
19
|
+
this.measureLCP();
|
|
20
|
+
this.measureTTFB();
|
|
21
|
+
this.measureFCP();
|
|
22
|
+
|
|
23
|
+
// Custom metrics
|
|
24
|
+
this.measurePageLoad();
|
|
25
|
+
this.measureResourceTiming();
|
|
26
|
+
|
|
27
|
+
// Report after page load
|
|
28
|
+
window.addEventListener('load', () => {
|
|
29
|
+
setTimeout(() => this.reportMetrics(), 2000);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
measureCLS() {
|
|
34
|
+
let clsValue = 0;
|
|
35
|
+
let clsEntries = [];
|
|
36
|
+
|
|
37
|
+
const observer = new PerformanceObserver((list) => {
|
|
38
|
+
for (const entry of list.getEntries()) {
|
|
39
|
+
if (!entry.hadRecentInput) {
|
|
40
|
+
clsValue += entry.value;
|
|
41
|
+
clsEntries.push(entry);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
observer.observe({type: 'layout-shift', buffered: true});
|
|
47
|
+
this.metrics.cls = () => clsValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
measureFID() {
|
|
51
|
+
const observer = new PerformanceObserver((list) => {
|
|
52
|
+
for (const entry of list.getEntries()) {
|
|
53
|
+
this.metrics.fid = entry.processingStart - entry.startTime;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
observer.observe({type: 'first-input', buffered: true});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
measureLCP() {
|
|
61
|
+
const observer = new PerformanceObserver((list) => {
|
|
62
|
+
const entries = list.getEntries();
|
|
63
|
+
const lastEntry = entries[entries.length - 1];
|
|
64
|
+
this.metrics.lcp = lastEntry.renderTime || lastEntry.loadTime;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
observer.observe({type: 'largest-contentful-paint', buffered: true});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
measureTTFB() {
|
|
71
|
+
const navTiming = performance.getEntriesByType('navigation')[0];
|
|
72
|
+
if (navTiming) {
|
|
73
|
+
this.metrics.ttfb = navTiming.responseStart - navTiming.requestStart;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
measureFCP() {
|
|
78
|
+
const observer = new PerformanceObserver((list) => {
|
|
79
|
+
for (const entry of list.getEntries()) {
|
|
80
|
+
if (entry.name === 'first-contentful-paint') {
|
|
81
|
+
this.metrics.fcp = entry.startTime;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
observer.observe({type: 'paint', buffered: true});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
measurePageLoad() {
|
|
90
|
+
window.addEventListener('load', () => {
|
|
91
|
+
const navTiming = performance.getEntriesByType('navigation')[0];
|
|
92
|
+
if (navTiming) {
|
|
93
|
+
this.metrics.domContentLoaded = navTiming.domContentLoadedEventEnd - navTiming.domContentLoadedEventStart;
|
|
94
|
+
this.metrics.loadComplete = navTiming.loadEventEnd - navTiming.loadEventStart;
|
|
95
|
+
this.metrics.totalTime = navTiming.loadEventEnd - navTiming.fetchStart;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
measureResourceTiming() {
|
|
101
|
+
window.addEventListener('load', () => {
|
|
102
|
+
const resources = performance.getEntriesByType('resource');
|
|
103
|
+
const byType = {};
|
|
104
|
+
|
|
105
|
+
resources.forEach(resource => {
|
|
106
|
+
const type = resource.initiatorType;
|
|
107
|
+
if (!byType[type]) byType[type] = {count: 0, totalSize: 0, totalDuration: 0};
|
|
108
|
+
|
|
109
|
+
byType[type].count++;
|
|
110
|
+
byType[type].totalSize += resource.transferSize || 0;
|
|
111
|
+
byType[type].totalDuration += resource.duration;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.metrics.resources = byType;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
reportMetrics() {
|
|
119
|
+
const report = {
|
|
120
|
+
url: window.location.href,
|
|
121
|
+
userAgent: navigator.userAgent,
|
|
122
|
+
connection: navigator.connection ? {
|
|
123
|
+
effectiveType: navigator.connection.effectiveType,
|
|
124
|
+
downlink: navigator.connection.downlink,
|
|
125
|
+
rtt: navigator.connection.rtt
|
|
126
|
+
} : null,
|
|
127
|
+
metrics: {
|
|
128
|
+
cls: typeof this.metrics.cls === 'function' ? this.metrics.cls() : this.metrics.cls,
|
|
129
|
+
fid: this.metrics.fid,
|
|
130
|
+
lcp: this.metrics.lcp,
|
|
131
|
+
ttfb: this.metrics.ttfb,
|
|
132
|
+
fcp: this.metrics.fcp,
|
|
133
|
+
domContentLoaded: this.metrics.domContentLoaded,
|
|
134
|
+
loadComplete: this.metrics.loadComplete,
|
|
135
|
+
totalTime: this.metrics.totalTime
|
|
136
|
+
},
|
|
137
|
+
resources: this.metrics.resources,
|
|
138
|
+
timestamp: new Date().toISOString()
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Log to console (in production, send to analytics endpoint)
|
|
142
|
+
console.log('Performance Metrics:', report);
|
|
143
|
+
|
|
144
|
+
// Store in localStorage for debugging
|
|
145
|
+
try {
|
|
146
|
+
const history = JSON.parse(localStorage.getItem('performanceHistory') || '[]');
|
|
147
|
+
history.push(report);
|
|
148
|
+
localStorage.setItem('performanceHistory', JSON.stringify(history.slice(-10))); // Keep last 10
|
|
149
|
+
} catch (e) {
|
|
150
|
+
console.error('Failed to store performance metrics:', e);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Send to analytics (if configured)
|
|
154
|
+
if (window.gtag) {
|
|
155
|
+
window.gtag('event', 'web_vitals', {
|
|
156
|
+
event_category: 'Performance',
|
|
157
|
+
cls: report.metrics.cls,
|
|
158
|
+
fid: report.metrics.fid,
|
|
159
|
+
lcp: report.metrics.lcp
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Initialize
|
|
166
|
+
if ('PerformanceObserver' in window) {
|
|
167
|
+
new PerformanceMonitor();
|
|
168
|
+
}
|
|
169
|
+
})();
|
|
170
|
+
</script>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
Popular Posts Widget Component
|
|
3
|
+
Displays trending and most popular content
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- max_posts: Maximum posts to display (default: 5)
|
|
7
|
+
- sort_by: Sorting method (recent, comments, trending) (default: recent)
|
|
8
|
+
- show_stats: Show reading stats (default: true)
|
|
9
|
+
- show_thumbnail: Show post thumbnail (default: true)
|
|
10
|
+
- layout: Display layout (list, grid, compact) (default: list)
|
|
11
|
+
- heading: Custom heading (default: "Popular Posts")
|
|
12
|
+
{%- endcomment -%}
|
|
13
|
+
|
|
14
|
+
{%- assign max_posts = include.max_posts | default: 5 -%}
|
|
15
|
+
{%- assign sort_by = include.sort_by | default: "recent" -%}
|
|
16
|
+
{%- assign show_stats = include.show_stats | default: true -%}
|
|
17
|
+
{%- assign show_thumbnail = include.show_thumbnail | default: true -%}
|
|
18
|
+
{%- assign layout = include.layout | default: "list" -%}
|
|
19
|
+
{%- assign heading = include.heading | default: "Popular Posts" -%}
|
|
20
|
+
|
|
21
|
+
{%- comment -%} Get posts and apply basic filtering {%- endcomment -%}
|
|
22
|
+
{%- assign all_posts = site.posts -%}
|
|
23
|
+
|
|
24
|
+
{%- comment -%} Simulate popularity scoring (in real app, use analytics data) {%- endcomment -%}
|
|
25
|
+
{%- assign scored_posts = "" | split: "" -%}
|
|
26
|
+
|
|
27
|
+
{%- for post in all_posts limit: 20 -%}
|
|
28
|
+
{%- assign score = 0 -%}
|
|
29
|
+
|
|
30
|
+
{%- comment -%} Recency score (prefer recent content) {%- endcomment -%}
|
|
31
|
+
{%- assign days_old = "now" | date: "%s" | minus: post.date | date: "%s" | divided_by: 86400 -%}
|
|
32
|
+
{%- if days_old < 7 -%}
|
|
33
|
+
{%- assign score = score | plus: 50 -%}
|
|
34
|
+
{%- elsif days_old < 30 -%}
|
|
35
|
+
{%- assign score = score | plus: 30 -%}
|
|
36
|
+
{%- elsif days_old < 90 -%}
|
|
37
|
+
{%- assign score = score | plus: 10 -%}
|
|
38
|
+
{%- endif -%}
|
|
39
|
+
|
|
40
|
+
{%- comment -%} Tag count (more tags = more discoverable) {%- endcomment -%}
|
|
41
|
+
{%- assign tag_score = post.tags.size | times: 5 -%}
|
|
42
|
+
{%- assign score = score | plus: tag_score -%}
|
|
43
|
+
|
|
44
|
+
{%- comment -%} Featured posts bonus {%- endcomment -%}
|
|
45
|
+
{%- if post.featured -%}
|
|
46
|
+
{%- assign score = score | plus: 25 -%}
|
|
47
|
+
{%- endif -%}
|
|
48
|
+
|
|
49
|
+
{%- comment -%} Difficulty-based scoring (beginner posts might be more popular) {%- endcomment -%}
|
|
50
|
+
{%- if post.difficulty == "beginner" -%}
|
|
51
|
+
{%- assign score = score | plus: 15 -%}
|
|
52
|
+
{%- endif -%}
|
|
53
|
+
|
|
54
|
+
{%- comment -%} Add randomness for variety (simulates view counts) {%- endcomment -%}
|
|
55
|
+
{%- assign random_bonus = post.title | size | modulo: 30 -%}
|
|
56
|
+
{%- assign score = score | plus: random_bonus -%}
|
|
57
|
+
|
|
58
|
+
{%- comment -%} Store post with score {%- endcomment -%}
|
|
59
|
+
{%- assign post_with_score = post.url | append: "||" | append: score -%}
|
|
60
|
+
{%- assign scored_posts = scored_posts | push: post_with_score -%}
|
|
61
|
+
{%- endfor -%}
|
|
62
|
+
|
|
63
|
+
{%- comment -%} Simple bubble sort by score {%- endcomment -%}
|
|
64
|
+
{%- assign sorted_posts = scored_posts -%}
|
|
65
|
+
{%- for i in (1..scored_posts.size) -%}
|
|
66
|
+
{%- for j in (1..scored_posts.size) -%}
|
|
67
|
+
{%- assign current = sorted_posts[j] | split: "||" -%}
|
|
68
|
+
{%- assign prev = sorted_posts[j-1] | split: "||" -%}
|
|
69
|
+
{%- if current[1] and prev[1] -%}
|
|
70
|
+
{%- assign current_score = current[1] | plus: 0 -%}
|
|
71
|
+
{%- assign prev_score = prev[1] | plus: 0 -%}
|
|
72
|
+
{%- if current_score > prev_score -%}
|
|
73
|
+
{%- assign temp = sorted_posts[j] -%}
|
|
74
|
+
{%- assign sorted_posts[j] = sorted_posts[j-1] -%}
|
|
75
|
+
{%- assign sorted_posts[j-1] = temp -%}
|
|
76
|
+
{%- endif -%}
|
|
77
|
+
{%- endif -%}
|
|
78
|
+
{%- endfor -%}
|
|
79
|
+
{%- endfor -%}
|
|
80
|
+
|
|
81
|
+
{%- comment -%} Build popular posts array {%- endcomment -%}
|
|
82
|
+
{%- assign popular_posts = "" | split: "" -%}
|
|
83
|
+
{%- for item in sorted_posts limit: max_posts -%}
|
|
84
|
+
{%- assign item_parts = item | split: "||" -%}
|
|
85
|
+
{%- assign post_url = item_parts[0] -%}
|
|
86
|
+
|
|
87
|
+
{%- for post in site.posts -%}
|
|
88
|
+
{%- if post.url == post_url -%}
|
|
89
|
+
{%- assign popular_posts = popular_posts | push: post -%}
|
|
90
|
+
{%- break -%}
|
|
91
|
+
{%- endif -%}
|
|
92
|
+
{%- endfor -%}
|
|
93
|
+
{%- endfor -%}
|
|
94
|
+
|
|
95
|
+
<aside class="popular-posts popular-posts--{{ layout }}"
|
|
96
|
+
data-popular-posts
|
|
97
|
+
aria-label="{{ heading }}">
|
|
98
|
+
|
|
99
|
+
<div class="popular-posts__header">
|
|
100
|
+
<h3 class="popular-posts__title">
|
|
101
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
102
|
+
width="20"
|
|
103
|
+
height="20"
|
|
104
|
+
viewBox="0 0 24 24"
|
|
105
|
+
fill="none"
|
|
106
|
+
stroke="currentColor"
|
|
107
|
+
stroke-width="2"
|
|
108
|
+
stroke-linecap="round"
|
|
109
|
+
stroke-linejoin="round"
|
|
110
|
+
aria-hidden="true">
|
|
111
|
+
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
|
112
|
+
<polyline points="17 6 23 6 23 12"></polyline>
|
|
113
|
+
</svg>
|
|
114
|
+
{{ heading }}
|
|
115
|
+
</h3>
|
|
116
|
+
|
|
117
|
+
{%- if show_stats -%}
|
|
118
|
+
<span class="popular-posts__subtitle">
|
|
119
|
+
Trending this month
|
|
120
|
+
</span>
|
|
121
|
+
{%- endif -%}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="popular-posts__list">
|
|
125
|
+
{%- for post in popular_posts -%}
|
|
126
|
+
{%- assign loop_index = forloop.index -%}
|
|
127
|
+
|
|
128
|
+
<article class="popular-post"
|
|
129
|
+
itemscope
|
|
130
|
+
itemtype="https://schema.org/BlogPosting">
|
|
131
|
+
|
|
132
|
+
<div class="popular-post__rank" aria-label="Rank {{ loop_index }}">
|
|
133
|
+
{{ loop_index }}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{%- if show_thumbnail and post.featured_image or post.image -%}
|
|
137
|
+
<div class="popular-post__thumbnail">
|
|
138
|
+
<a href="{{ post.url | relative_url }}"
|
|
139
|
+
tabindex="-1"
|
|
140
|
+
aria-hidden="true">
|
|
141
|
+
<img src="{{ post.featured_image | default: post.image | relative_url }}"
|
|
142
|
+
alt=""
|
|
143
|
+
loading="lazy"
|
|
144
|
+
itemprop="image">
|
|
145
|
+
</a>
|
|
146
|
+
</div>
|
|
147
|
+
{%- endif -%}
|
|
148
|
+
|
|
149
|
+
<div class="popular-post__content">
|
|
150
|
+
<h4 class="popular-post__title" itemprop="headline">
|
|
151
|
+
<a href="{{ post.url | relative_url }}"
|
|
152
|
+
class="popular-post__link">
|
|
153
|
+
{{ post.title }}
|
|
154
|
+
</a>
|
|
155
|
+
</h4>
|
|
156
|
+
|
|
157
|
+
{%- if show_stats -%}
|
|
158
|
+
<div class="popular-post__meta">
|
|
159
|
+
{%- if post.date -%}
|
|
160
|
+
<time class="popular-post__date"
|
|
161
|
+
datetime="{{ post.date | date_to_xmlschema }}"
|
|
162
|
+
itemprop="datePublished">
|
|
163
|
+
{{ post.date | date: "%b %d, %Y" }}
|
|
164
|
+
</time>
|
|
165
|
+
{%- endif -%}
|
|
166
|
+
|
|
167
|
+
<span class="popular-post__separator" aria-hidden="true">•</span>
|
|
168
|
+
|
|
169
|
+
{%- comment -%} Reading time {%- endcomment -%}
|
|
170
|
+
{% include components/reading-time.html
|
|
171
|
+
content=post.content
|
|
172
|
+
variant="inline"
|
|
173
|
+
show_icon=false %}
|
|
174
|
+
|
|
175
|
+
{%- comment -%} View count removed — was simulated. Re-enable with real analytics data. {%- endcomment -%}
|
|
176
|
+
</div>
|
|
177
|
+
{%- endif -%}
|
|
178
|
+
|
|
179
|
+
{%- if post.excerpt and layout != "compact" -%}
|
|
180
|
+
<p class="popular-post__excerpt" itemprop="description">
|
|
181
|
+
{{ post.excerpt | strip_html | truncatewords: 15 }}
|
|
182
|
+
</p>
|
|
183
|
+
{%- endif -%}
|
|
184
|
+
|
|
185
|
+
{%- if post.tags.size > 0 and layout == "grid" -%}
|
|
186
|
+
<div class="popular-post__tags">
|
|
187
|
+
{%- for tag in post.tags limit: 2 -%}
|
|
188
|
+
<span class="popular-post__tag">{{ tag }}</span>
|
|
189
|
+
{%- endfor -%}
|
|
190
|
+
</div>
|
|
191
|
+
{%- endif -%}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{%- comment -%} Trending indicator for top 3 {%- endcomment -%}
|
|
195
|
+
{%- if loop_index <= 3 -%}
|
|
196
|
+
<div class="popular-post__badge" aria-label="Trending">
|
|
197
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
198
|
+
width="16"
|
|
199
|
+
height="16"
|
|
200
|
+
viewBox="0 0 24 24"
|
|
201
|
+
fill="none"
|
|
202
|
+
stroke="currentColor"
|
|
203
|
+
stroke-width="2"
|
|
204
|
+
aria-hidden="true">
|
|
205
|
+
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
|
206
|
+
<polyline points="17 6 23 6 23 12"></polyline>
|
|
207
|
+
</svg>
|
|
208
|
+
</div>
|
|
209
|
+
{%- endif -%}
|
|
210
|
+
</article>
|
|
211
|
+
{%- endfor -%}
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{%- if site.phase4_features.popular_posts.show_view_all -%}
|
|
215
|
+
<div class="popular-posts__footer">
|
|
216
|
+
<a href="{{ '/archive/' | relative_url }}"
|
|
217
|
+
class="popular-posts__view-all">
|
|
218
|
+
View all posts
|
|
219
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
220
|
+
width="16"
|
|
221
|
+
height="16"
|
|
222
|
+
viewBox="0 0 24 24"
|
|
223
|
+
fill="none"
|
|
224
|
+
stroke="currentColor"
|
|
225
|
+
stroke-width="2"
|
|
226
|
+
aria-hidden="true">
|
|
227
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
228
|
+
<polyline points="12 5 19 12 12 19"></polyline>
|
|
229
|
+
</svg>
|
|
230
|
+
</a>
|
|
231
|
+
</div>
|
|
232
|
+
{%- endif -%}
|
|
233
|
+
</aside>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{%- comment -%}
|
|
2
|
+
---
|
|
3
|
+
required:
|
|
4
|
+
- page
|
|
5
|
+
---
|
|
6
|
+
Renders a post hero from the `post_hero` hash, which the Minimal Mistakes
|
|
7
|
+
compatibility hook (_plugins/front_matter_compat.rb) builds from `header.*`
|
|
8
|
+
fields and which can also be written directly:
|
|
9
|
+
|
|
10
|
+
post_hero:
|
|
11
|
+
image: /assets/images/hero.jpg
|
|
12
|
+
overlay: true # title over the image, as Minimal Mistakes does
|
|
13
|
+
overlay_filter: 0.35 # number, rgba(...) or a gradient
|
|
14
|
+
overlay_color: "#123"
|
|
15
|
+
alt: Description of the image (figure variant)
|
|
16
|
+
caption: Caption under the image (figure variant)
|
|
17
|
+
|
|
18
|
+
The overlay variant sets its background through a nonce-carrying <style>
|
|
19
|
+
element rather than a style attribute, because the theme's Content Security
|
|
20
|
+
Policy does not allow inline style attributes.
|
|
21
|
+
{%- endcomment -%}
|
|
22
|
+
{%- assign hero = include.page.post_hero -%}
|
|
23
|
+
{%- assign hero_src = hero.image -%}
|
|
24
|
+
{%- unless hero_src contains '://' -%}
|
|
25
|
+
{%- assign hero_src = hero_src | relative_url -%}
|
|
26
|
+
{%- endunless -%}
|
|
27
|
+
{%- assign hero_alt = hero.alt | default: include.page.title | default: '' | strip_html | strip_newlines -%}
|
|
28
|
+
{%- assign overlay_filter = '' -%}
|
|
29
|
+
{%- if hero.overlay_filter -%}
|
|
30
|
+
{%- assign filter_value = hero.overlay_filter | append: '' -%}
|
|
31
|
+
{%- if filter_value contains 'gradient' -%}
|
|
32
|
+
{%- assign overlay_filter = filter_value -%}
|
|
33
|
+
{%- elsif filter_value contains 'rgba' -%}
|
|
34
|
+
{%- assign overlay_filter = 'linear-gradient(' | append: filter_value | append: ', ' | append: filter_value | append: ')' -%}
|
|
35
|
+
{%- else -%}
|
|
36
|
+
{%- assign rgba = 'rgba(0, 0, 0, ' | append: filter_value | append: ')' -%}
|
|
37
|
+
{%- assign overlay_filter = 'linear-gradient(' | append: rgba | append: ', ' | append: rgba | append: ')' -%}
|
|
38
|
+
{%- endif -%}
|
|
39
|
+
{%- endif -%}
|
|
40
|
+
{%- if hero.overlay -%}
|
|
41
|
+
{%- assign hero_id = include.page.url | default: 'page' | slugify | prepend: 'post-hero-' -%}
|
|
42
|
+
<style nonce="{{ page.csp_nonce }}">
|
|
43
|
+
#{{ hero_id }} { {% if hero.overlay_color %}background-color: {{ hero.overlay_color }}; {% endif %}background-image: {% if overlay_filter != '' %}{{ overlay_filter }}, {% endif %}url('{{ hero_src }}'); }
|
|
44
|
+
</style>
|
|
45
|
+
<div id="{{ hero_id }}" class="post-hero post-hero--overlay">
|
|
46
|
+
<div class="post-hero__inner">
|
|
47
|
+
{%- if include.page.title -%}
|
|
48
|
+
<h1 class="article-title post-hero__title" itemprop="headline">{{ include.page.title }}</h1>
|
|
49
|
+
{%- endif -%}
|
|
50
|
+
{%- if include.page.subtitle -%}
|
|
51
|
+
<p class="post-hero__subtitle">{{ include.page.subtitle }}</p>
|
|
52
|
+
{%- endif -%}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
{%- else -%}
|
|
56
|
+
<figure class="post-hero">
|
|
57
|
+
<img class="post-hero__image" src="{{ hero_src }}" alt="{{ hero_alt | escape_once }}" loading="eager" decoding="async" />
|
|
58
|
+
{%- if hero.caption -%}
|
|
59
|
+
<figcaption class="post-hero__caption">{{ hero.caption | markdownify | strip_html | strip_newlines }}</figcaption>
|
|
60
|
+
{%- endif -%}
|
|
61
|
+
</figure>
|
|
62
|
+
{%- endif -%}
|